@pm-cm/yjs 0.0.12 → 0.0.13

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/dist/index.cjs CHANGED
@@ -141,6 +141,9 @@ function createYjsBridge(config, options) {
141
141
  throw new Error("sharedProseMirror belongs to a different Y.Doc than the provided doc");
142
142
  }
143
143
  let lastBridgedText = null;
144
+ let pendingSkipCleanup = null;
145
+ let skipPending = false;
146
+ let parseFailed = false;
144
147
  const syncTextToProsemirror = (origin) => {
145
148
  const text = normalize(sharedText.toString());
146
149
  if (lastBridgedText === text) {
@@ -153,6 +156,7 @@ function createYjsBridge(config, options) {
153
156
  onError
154
157
  });
155
158
  if (result.ok) {
159
+ parseFailed = false;
156
160
  let canonical = text;
157
161
  if (origin === ORIGIN_INIT) {
158
162
  try {
@@ -165,6 +169,9 @@ function createYjsBridge(config, options) {
165
169
  }
166
170
  }
167
171
  lastBridgedText = canonical;
172
+ } else {
173
+ parseFailed = true;
174
+ lastBridgedText = text;
168
175
  }
169
176
  return result.ok;
170
177
  };
@@ -226,13 +233,17 @@ function createYjsBridge(config, options) {
226
233
  let parseError = false;
227
234
  if (fallbackText.length > 0) {
228
235
  replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize);
236
+ lastBridgedText = normalize(fallbackText);
229
237
  const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {
230
238
  schema,
231
239
  parse,
232
240
  normalize,
233
241
  onError
234
242
  });
235
- if (!fallbackResult.ok) parseError = true;
243
+ if (!fallbackResult.ok) {
244
+ parseError = true;
245
+ parseFailed = true;
246
+ }
236
247
  }
237
248
  return { source: "text", ...parseError && { parseError: true } };
238
249
  }
@@ -257,10 +268,57 @@ function createYjsBridge(config, options) {
257
268
  }
258
269
  if (transactionTouchedXmlFragment(transaction.changed, sharedProseMirror)) {
259
270
  lastBridgedText = normalize(sharedText.toString());
271
+ parseFailed = false;
260
272
  return;
261
273
  }
262
274
  if (skipOrigins !== null && skipOrigins.has(transaction.origin)) {
263
- lastBridgedText = normalize(sharedText.toString());
275
+ const expectedText = normalize(sharedText.toString());
276
+ lastBridgedText = expectedText;
277
+ const pmTextNow = sharedProseMirrorToText(sharedProseMirror);
278
+ if (pmTextNow !== null && normalize(pmTextNow) === expectedText) {
279
+ parseFailed = false;
280
+ if (pendingSkipCleanup) pendingSkipCleanup();
281
+ return;
282
+ }
283
+ const xmlTextAtSkipStart = pmTextNow;
284
+ if (pendingSkipCleanup) pendingSkipCleanup();
285
+ skipPending = true;
286
+ let resolved = false;
287
+ const resolve = () => {
288
+ if (resolved) return;
289
+ resolved = true;
290
+ skipPending = false;
291
+ sharedProseMirror.unobserveDeep(xmlCatchUpObserver);
292
+ clearTimeout(timer);
293
+ pendingSkipCleanup = null;
294
+ };
295
+ const xmlCatchUpObserver = (_events, transaction2) => {
296
+ if (resolved) return;
297
+ if (skipOrigins.has(transaction2.origin)) {
298
+ const pmText = sharedProseMirrorToText(sharedProseMirror);
299
+ if (pmText !== null && normalize(pmText) === expectedText) {
300
+ parseFailed = false;
301
+ resolve();
302
+ }
303
+ }
304
+ };
305
+ const runFallback = () => {
306
+ resolve();
307
+ if (lastBridgedText !== expectedText) return;
308
+ const pmText = sharedProseMirrorToText(sharedProseMirror);
309
+ if (pmText === null || normalize(pmText) !== expectedText) {
310
+ const changedDuringSkip = xmlTextAtSkipStart !== null && pmText !== null && normalize(pmText) !== normalize(xmlTextAtSkipStart) || xmlTextAtSkipStart === null && pmText !== null || xmlTextAtSkipStart !== null && pmText === null;
311
+ if (changedDuringSkip) {
312
+ lastBridgedText = pmText !== null ? normalize(pmText) : lastBridgedText;
313
+ return;
314
+ }
315
+ lastBridgedText = null;
316
+ syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
317
+ }
318
+ };
319
+ const timer = setTimeout(runFallback, 500);
320
+ sharedProseMirror.observeDeep(xmlCatchUpObserver);
321
+ pendingSkipCleanup = resolve;
264
322
  return;
265
323
  }
266
324
  syncTextToProsemirror(ORIGIN_TEXT_TO_PM);
@@ -270,6 +328,12 @@ function createYjsBridge(config, options) {
270
328
  return {
271
329
  bootstrapResult,
272
330
  syncToSharedText(doc2) {
331
+ if (skipPending) {
332
+ return { ok: false, reason: "skip-pending" };
333
+ }
334
+ if (parseFailed) {
335
+ return { ok: false, reason: "parse-failed" };
336
+ }
273
337
  let text;
274
338
  try {
275
339
  text = serialize(doc2);
@@ -289,6 +353,7 @@ function createYjsBridge(config, options) {
289
353
  },
290
354
  dispose() {
291
355
  sharedText.unobserve(textObserver);
356
+ if (pendingSkipCleanup) pendingSkipCleanup();
292
357
  }
293
358
  };
294
359
  }
@@ -340,7 +405,6 @@ var wiredBridges = /* @__PURE__ */ new WeakMap();
340
405
  var defaultOnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
341
406
  function createBridgeSyncPlugin(bridge, options = {}) {
342
407
  const warn = options.onWarning ?? defaultOnWarning;
343
- let yjsBatchSeen = false;
344
408
  let needsSync = false;
345
409
  return new import_prosemirror_state.Plugin({
346
410
  key: bridgeSyncPluginKey,
@@ -351,10 +415,6 @@ function createBridgeSyncPlugin(bridge, options = {}) {
351
415
  apply(tr, _prev) {
352
416
  if (!tr.docChanged) return { needsSync };
353
417
  if (bridge.isYjsSyncChange(tr)) {
354
- yjsBatchSeen = true;
355
- return { needsSync };
356
- }
357
- if (yjsBatchSeen && tr.getMeta("appendedTransaction")) {
358
418
  return { needsSync };
359
419
  }
360
420
  needsSync = true;
@@ -372,6 +432,14 @@ function createBridgeSyncPlugin(bridge, options = {}) {
372
432
  if (needsSync) {
373
433
  const result = bridge.syncToSharedText(view.state.doc);
374
434
  if (!result.ok) {
435
+ if (result.reason === "skip-pending") {
436
+ return;
437
+ }
438
+ if (result.reason === "parse-failed") {
439
+ options.onSyncFailure?.(result, view);
440
+ warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
441
+ return;
442
+ }
375
443
  if (result.reason !== "unchanged") {
376
444
  options.onSyncFailure?.(result, view);
377
445
  warn({ code: "sync-failed", message: `bridge sync failed: ${result.reason}` });
@@ -379,12 +447,15 @@ function createBridgeSyncPlugin(bridge, options = {}) {
379
447
  }
380
448
  }
381
449
  needsSync = false;
382
- yjsBatchSeen = false;
383
450
  },
384
451
  destroy() {
385
452
  const remaining = (wiredBridges.get(bridge) ?? 1) - 1;
386
- if (remaining <= 0) wiredBridges.delete(bridge);
387
- else wiredBridges.set(bridge, remaining);
453
+ if (remaining <= 0) {
454
+ wiredBridges.delete(bridge);
455
+ if (options.autoDispose) bridge.dispose();
456
+ } else {
457
+ wiredBridges.set(bridge, remaining);
458
+ }
388
459
  }
389
460
  };
390
461
  }
@@ -430,6 +501,8 @@ function broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAncho
430
501
  awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead });
431
502
  }
432
503
  var defaultOnWarning2 = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`);
504
+ var awarenessRefCounts = /* @__PURE__ */ new WeakMap();
505
+ var awarenessFieldNames = /* @__PURE__ */ new WeakMap();
433
506
  function createCursorSyncPlugin(options) {
434
507
  const { awareness, serialize, sharedText } = options;
435
508
  const warn = options.onWarning ?? defaultOnWarning2;
@@ -479,6 +552,17 @@ function createCursorSyncPlugin(options) {
479
552
  }
480
553
  },
481
554
  view(editorView) {
555
+ const viewCount = awarenessRefCounts.get(awareness) ?? 0;
556
+ awarenessRefCounts.set(awareness, viewCount + 1);
557
+ let fieldNames = awarenessFieldNames.get(awareness);
558
+ if (!fieldNames) {
559
+ fieldNames = /* @__PURE__ */ new Set();
560
+ awarenessFieldNames.set(awareness, fieldNames);
561
+ }
562
+ fieldNames.add(cursorFieldName);
563
+ if (sharedText) {
564
+ fieldNames.add(cmCursorFieldName);
565
+ }
482
566
  let suppressCmReaction = false;
483
567
  let lastCmAbsAnchor = -1;
484
568
  let lastCmAbsHead = -1;
@@ -511,6 +595,9 @@ function createCursorSyncPlugin(options) {
511
595
  lastCmAbsHead = -1;
512
596
  return;
513
597
  }
598
+ if (absAnchor.type !== sharedText || absHead.type !== sharedText) {
599
+ return;
600
+ }
514
601
  if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return;
515
602
  const map = getOrBuildMap(editorView.state.doc);
516
603
  if (!map) {
@@ -525,12 +612,18 @@ function createCursorSyncPlugin(options) {
525
612
  }
526
613
  lastCmAbsAnchor = absAnchor.index;
527
614
  lastCmAbsHead = absHead.index;
528
- cmCursorHandledByListener = true;
615
+ if (pendingCmCursor != null) {
616
+ cmCursorHandledByListener = true;
617
+ }
529
618
  const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead);
530
619
  if (!ok && !warnedSyncPluginMissing) {
531
620
  warnedSyncPluginMissing = true;
532
621
  warn({ code: "ysync-plugin-missing", message: "ySyncPlugin state not available \u2014 cursor broadcast skipped" });
533
622
  }
623
+ } catch {
624
+ awareness.setLocalStateField(cursorFieldName, null);
625
+ lastCmAbsAnchor = -1;
626
+ lastCmAbsHead = -1;
534
627
  } finally {
535
628
  inAwarenessHandler = false;
536
629
  }
@@ -571,6 +664,9 @@ function createCursorSyncPlugin(options) {
571
664
  }
572
665
  } else {
573
666
  awareness.setLocalStateField(cursorFieldName, null);
667
+ if (sharedText?.doc) {
668
+ awareness.setLocalStateField(cmCursorFieldName, null);
669
+ }
574
670
  }
575
671
  }
576
672
  cmCursorHandledByListener = false;
@@ -605,9 +701,18 @@ function createCursorSyncPlugin(options) {
605
701
  if (sharedText) {
606
702
  awareness.off("update", handleAwarenessUpdate);
607
703
  }
608
- awareness.setLocalStateField(cursorFieldName, null);
609
- if (sharedText) {
610
- awareness.setLocalStateField(cmCursorFieldName, null);
704
+ const remaining = (awarenessRefCounts.get(awareness) ?? 1) - 1;
705
+ if (remaining <= 0) {
706
+ awarenessRefCounts.delete(awareness);
707
+ const fields = awarenessFieldNames.get(awareness);
708
+ if (fields) {
709
+ for (const field of fields) {
710
+ awareness.setLocalStateField(field, null);
711
+ }
712
+ awarenessFieldNames.delete(awareness);
713
+ }
714
+ } else {
715
+ awarenessRefCounts.set(awareness, remaining);
611
716
  }
612
717
  }
613
718
  };
@@ -648,7 +753,10 @@ function createCollabPlugins(schema, options) {
648
753
  (0, import_y_prosemirror3.yUndoPlugin)(options.yUndoPluginOpts)
649
754
  ];
650
755
  if (options.bridge) {
651
- plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }));
756
+ plugins.push(createBridgeSyncPlugin(options.bridge, {
757
+ onWarning: options.onWarning,
758
+ autoDispose: options.autoDisposeBridge
759
+ }));
652
760
  }
653
761
  if (enableCursorSync && options.serialize) {
654
762
  plugins.push(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts"],"sourcesContent":["export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, SerializeWithMap, CursorMapWriter, Matcher, MatchResult, MatchRun } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport { createCursorMapWriter, wrapSerialize } from '@pm-cm/core'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n","import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/**\n * Check whether any type modified in the transaction belongs to the\n * given XmlFragment subtree (the fragment itself or any descendant).\n *\n * Uses the Yjs internal `_item.parent` chain which has been stable\n * across the 13.x series and is also relied on by y-prosemirror.\n */\nfunction transactionTouchedXmlFragment(\n changed: Map<object, Set<string | null>>,\n xmlFragment: YXmlFragment,\n): boolean {\n for (const type of changed.keys()) {\n if (type === xmlFragment) return true\n // Walk the parent chain from the changed type up to the root.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let item = (type as any)._item\n while (item) {\n if (item.parent === xmlFragment) return true\n item = item.parent?._item ?? null\n }\n }\n return false\n}\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n | { ok: false; reason: 'serialize-error' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n return { ok: true }\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n const skipOrigins = config.skipOrigins ?? null\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok) {\n // Canonicalize only during bootstrap (ORIGIN_INIT).\n //\n // During live editing the text may be in an intermediate state where\n // serialize(parse(text)) !== text (e.g. partially typed syntax that the\n // parser interprets differently from the user's intent). Writing the\n // canonical form back to Y.Text in that situation corrupts the document\n // and can trigger reentrant updates in editor view-plugins that observe\n // Y.Text changes.\n //\n // At bootstrap time the text is well-formed (loaded from persistence),\n // so the round-trip is idempotent and canonicalization ensures both\n // shared types agree for future bridge mounts (`both-match`).\n let canonical = text\n if (origin === ORIGIN_INIT) {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n canonical = normalize(serialize(pmDoc))\n if (canonical !== text) {\n replaceSharedText(sharedText, canonical, ORIGIN_INIT, normalize)\n }\n } catch {\n // Serialization failure during canonicalization is non-fatal;\n // fall back to using the original text as lastBridgedText.\n }\n }\n lastBridgedText = canonical\n }\n return result.ok\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n let canonicalText: string\n try {\n canonicalText = serialize(pmDoc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document during bootstrap', cause: error })\n return { source: 'initial', parseError: true }\n }\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) parseError = true\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown; changed: Map<object, Set<string | null>> },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n // When the same transaction also modified the XmlFragment subtree\n // (e.g. a remote Y.Doc update applied via Y.applyUpdate that modified\n // both shared types atomically), ySyncPlugin already handles the\n // XmlFragment → PM direction. Calling syncTextToProsemirror would\n // redundantly reconstruct the XmlFragment via prosemirrorToYXmlFragment,\n // destroying existing Yjs Item IDs and invalidating cursor\n // RelativePositions held by peers in Awareness state.\n if (transactionTouchedXmlFragment(transaction.changed, sharedProseMirror)) {\n lastBridgedText = normalize(sharedText.toString())\n return\n }\n\n // When multiple clients run a bridge, a remote Y.Text change and its\n // corresponding Y.XmlFragment change arrive as separate transactions.\n // Writing to XmlFragment here would race with the remote XmlFragment\n // update, causing the CRDT to keep both insertions (duplicate nodes).\n // Skip the sync and let the remote XmlFragment update handle it.\n if (skipOrigins !== null && skipOrigins.has(transaction.origin)) {\n lastBridgedText = normalize(sharedText.toString())\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n let text: string\n try {\n text = serialize(doc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document', cause: error })\n return { ok: false, reason: 'serialize-error' }\n }\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed' | 'cursor-map-error'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n * - `'cursor-map-error'` — failed to build cursor map (serialize threw); cursor sync skipped.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n /**\n * Set of Y.Doc transaction origins for which the bridge should NOT run\n * text → ProseMirror sync.\n *\n * **When to use:** When multiple clients each run a bridge, a remote\n * Y.Text change and its corresponding Y.XmlFragment change may arrive\n * as separate transactions (they originate from separate `doc.transact`\n * calls and travel as separate provider messages). Without this option\n * the receiving bridge writes to XmlFragment for the Y.Text-only\n * transaction, and the subsequent CRDT merge with the remote\n * XmlFragment update creates duplicate nodes (especially empty\n * paragraphs whose content is identical).\n *\n * **Important:** Only include origins where every Y.Text change is\n * guaranteed to be accompanied by a corresponding Y.XmlFragment update\n * (i.e. the sender also runs a bridge). Do **not** include origins used\n * by text-only producers (e.g. a CodeMirror-only peer without a bridge),\n * or ProseMirror will miss those changes and become stale.\n *\n * @example\n * ```ts\n * // All peers run a bridge — safe to skip remote origins\n * skipOrigins: new Set(['remote'])\n * ```\n */\n skipOrigins?: Set<unknown>\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync\n * plugin's awareness field for y-prosemirror's `yCursorPlugin`.\n *\n * y-prosemirror (npm 1.3.7) hardcodes `\"cursor\"` in `createDecorations`,\n * but the cursor sync plugin writes to a separate field (default `\"pmCursor\"`)\n * to avoid conflicts with y-codemirror.next's `\"cursor\"` (Y.Text-based).\n *\n * This proxy:\n * - **`getStates()`**: remaps `cursorField` → `\"cursor\"` so yCursorPlugin\n * finds the PM cursor data under the hardcoded `\"cursor\"` key.\n * - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's\n * `updateCursorInfo` never tries to broadcast its own cursor.\n * - **`setLocalStateField(\"cursor\", …)`**: suppressed (no-op) so\n * yCursorPlugin cannot overwrite the field managed by the sync plugin.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n // Hide \"cursor\" so yCursorPlugin's updateCursorInfo sees null\n return state ? { ...state, cursor: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Block yCursorPlugin's writes to \"cursor\"\n if (field === 'cursor') return\n target.setLocalStateField(field, value)\n }\n }\n if (prop === 'getStates') {\n return () => {\n const states = target.getStates()\n // Remap cursorField → \"cursor\" so yCursorPlugin reads PM cursor data.\n // Only override when the client actually has cursorField — clients\n // without cursor sync (e.g. using yCursorPlugin directly) keep their\n // original \"cursor\" intact.\n const remapped = new Map<number, Record<string, unknown>>()\n states.forEach((state, clientId) => {\n const s = state as Record<string, unknown>\n if (cursorField in s) {\n remapped.set(clientId, { ...s, cursor: s[cursorField] ?? null })\n } else {\n remapped.set(clientId, s)\n }\n })\n return remapped\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, SerializeWithMap } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize | SerializeWithMap\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, { onWarning: options.onWarning }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' | 'serialize-error' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakMap<YjsBridgeHandle, number>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n\n // Tracks whether any Yjs-originated docChange was seen in the current\n // dispatch batch. Used to suppress follow-up appendTransactions (e.g.\n // prosemirror-tables normalization) that lack ySyncPlugin meta but are\n // derived from the same Yjs update. Only suppresses transactions that\n // carry ProseMirror's \"appendedTransaction\" meta — genuine user edits\n // (direct dispatches) always propagate to Y.Text.\n let yjsBatchSeen = false\n // Closure flag consumed in view.update — avoids sticky plugin state across dispatches.\n let needsSync = false\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync }\n if (bridge.isYjsSyncChange(tr)) {\n yjsBatchSeen = true\n return { needsSync }\n }\n // Follow-up appendTransaction after a ySync change (e.g.\n // prosemirror-tables normalization triggered by a remote update):\n // suppress to avoid redundant serialization. Direct user edits\n // never carry \"appendedTransaction\" meta.\n if (yjsBatchSeen && tr.getMeta('appendedTransaction')) {\n return { needsSync }\n }\n needsSync = true\n return { needsSync: true }\n },\n },\n\n view() {\n const count = wiredBridges.get(bridge) ?? 0\n if (count > 0) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.set(bridge, count + 1)\n\n return {\n update(view) {\n if (needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n if (result.reason !== 'unchanged') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n needsSync = false\n yjsBatchSeen = false\n },\n destroy() {\n const remaining = (wiredBridges.get(bridge) ?? 1) - 1\n if (remaining <= 0) wiredBridges.delete(bridge)\n else wiredBridges.set(bridge, remaining)\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex, createAbsolutePositionFromRelativePosition } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment, RelativePosition } from 'yjs'\nimport type { Serialize, SerializeWithMap, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize | SerializeWithMap\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n if (sharedText && cursorFieldName === cmCursorFieldName) {\n throw new Error(\n `createCursorSyncPlugin: cursorFieldName and cmCursorFieldName must differ when sharedText is provided (both are \"${cursorFieldName}\")`,\n )\n }\n\n let warnedSyncPluginMissing = false\n\n // Closure variable consumed in view.update — survives appendTransaction\n // that would otherwise clear the plugin state before update() runs.\n let pendingCmCursor: { anchor: number; head: number } | null = null\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap | null {\n if (cachedMapDoc !== doc || !cachedMap) {\n try {\n cachedMap = buildCursorMap(doc, serialize)\n } catch (error) {\n warn({ code: 'cursor-map-error', message: 'failed to build cursor map — cursor sync skipped' })\n cachedMap = null\n }\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n pendingCmCursor = cmMeta\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = map ? cursorMapLookup(map, newState.selection.anchor) : null\n }\n\n return {\n pendingCm: pendingCmCursor,\n mappedTextOffset,\n }\n },\n },\n\n view(editorView) {\n // Suppress awareness listener reactions when PM broadcasts to cmCursorFieldName\n let suppressCmReaction = false\n // Track last resolved CM cursor to avoid redundant pmCursor broadcasts\n let lastCmAbsAnchor = -1\n let lastCmAbsHead = -1\n // Set when the awareness listener already broadcast pmCursor for the\n // current CM cursor change, so pendingCm can skip its own broadcast.\n let cmCursorHandledByListener = false\n\n // When sharedText is available, yCollab writes the CM cursor (including\n // range selections) to awareness[cmCursorFieldName]. This listener\n // converts those Y.Text relative positions to PM positions and\n // broadcasts to pmCursor — so the app never needs to forward CM ranges.\n // Guard against reentrant calls: awareness.setLocalStateField emits\n // 'update' synchronously, which would re-enter handleAwarenessUpdate.\n // Without this guard, cursor resolution failure → setLocalStateField(null)\n // → 'update' event → handleAwarenessUpdate → resolution failure again\n // → setLocalStateField(null) → infinite recursion → stack overflow.\n let inAwarenessHandler = false\n const handleAwarenessUpdate = (\n { added, updated }: { added: number[]; updated: number[]; removed: number[] },\n ) => {\n if (inAwarenessHandler) return\n if (suppressCmReaction) return\n if (!sharedText?.doc) return\n const localId = awareness.clientID\n if (!updated.includes(localId) && !added.includes(localId)) return\n\n const localState = awareness.getLocalState() as Record<string, unknown> | null\n if (!localState) return\n\n inAwarenessHandler = true\n try {\n const cmCursor = localState[cmCursorFieldName] as\n | { anchor: RelativePosition; head: RelativePosition }\n | undefined\n if (!cmCursor?.anchor || !cmCursor?.head) {\n // CM cursor was cleared — also clear pmCursor if it was previously set\n if (lastCmAbsAnchor !== -1) {\n awareness.setLocalStateField(cursorFieldName, null)\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n }\n return\n }\n\n const absAnchor = createAbsolutePositionFromRelativePosition(cmCursor.anchor, sharedText.doc!)\n const absHead = createAbsolutePositionFromRelativePosition(cmCursor.head, sharedText.doc!)\n if (!absAnchor || !absHead) {\n // Relative position can't be resolved — clear stale PM cursor\n awareness.setLocalStateField(cursorFieldName, null)\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n return\n }\n\n // Skip if unchanged (prevents loops from our own pmCursor broadcast)\n if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return\n\n const map = getOrBuildMap(editorView.state.doc)\n if (!map) {\n awareness.setLocalStateField(cursorFieldName, null)\n return\n }\n const pmAnchor = reverseCursorMapLookup(map, absAnchor.index)\n const pmHead = reverseCursorMapLookup(map, absHead.index)\n if (pmAnchor === null || pmHead === null) {\n // Mapping failed — clear stale PM cursor\n awareness.setLocalStateField(cursorFieldName, null)\n return\n }\n\n // Only update dedup cache after successful mapping — failed mapping\n // must not prevent retry when the doc changes and the map resolves.\n lastCmAbsAnchor = absAnchor.index\n lastCmAbsHead = absHead.index\n\n cmCursorHandledByListener = true\n const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n } finally {\n inAwarenessHandler = false\n }\n }\n\n if (sharedText) {\n awareness.on('update', handleAwarenessUpdate)\n }\n\n return {\n update(view, prevState) {\n // Invalidate dedup cache when doc changes so awareness listener\n // re-broadcasts pmCursor even if text offsets haven't changed.\n // Also re-trigger the handler to resolve cursors that failed mapping\n // when the doc was stale (no new awareness update would fire).\n if (view.state.doc !== prevState.doc) {\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n if (sharedText?.doc && !suppressCmReaction) {\n handleAwarenessUpdate({ added: [awareness.clientID], updated: [], removed: [] })\n }\n }\n\n // CM → awareness: broadcast when pendingCmCursor is set.\n // Uses closure variable so appendTransaction cannot clear it.\n // If the awareness listener already converted yCollab's range to\n // pmCursor, skip here to avoid overwriting with collapsed data.\n if (pendingCmCursor != null) {\n const cursor = pendingCmCursor\n pendingCmCursor = null\n if (!cmCursorHandledByListener) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = map ? reverseCursorMapLookup(map, cursor.anchor) : null\n const pmHead = map ? reverseCursorMapLookup(map, cursor.head) : null\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // (When yCollab is active the listener handles this; this path\n // covers the case where syncCmCursor is called without yCollab.)\n if (sharedText?.doc) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n cursor.anchor,\n cursor.head,\n )\n }\n } else {\n // Mapping failed — clear stale PM cursor\n awareness.setLocalStateField(cursorFieldName, null)\n }\n }\n cmCursorHandledByListener = false\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused.\n // suppressCmReaction wraps ALL awareness writes so the listener\n // does not echo stale CM cursor back to pmCursor.\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n suppressCmReaction = true\n try {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n if (sharedText?.doc) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = map ? cursorMapLookup(map, anchor) : null\n const textHead = map ? cursorMapLookup(map, head) : null\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n } else {\n // Mapping failed — clear stale CM cursor\n awareness.setLocalStateField(cmCursorFieldName, null)\n }\n }\n } finally {\n suppressCmReaction = false\n }\n }\n\n // Always reset so a stale flag from a previous awareness update\n // cannot suppress a later syncCmCursor broadcast.\n cmCursorHandledByListener = false\n },\n destroy() {\n // Remove listener BEFORE clearing fields to avoid infinite\n // recursion: setLocalStateField emits 'update' synchronously,\n // which would re-enter handleAwarenessUpdate while the cursor\n // field is still set, causing broadcastPmCursor → update → …\n if (sharedText) {\n awareness.off('update', handleAwarenessUpdate)\n }\n // Clear cursor fields so remote clients don't see ghost cursors\n awareness.setLocalStateField(cursorFieldName, null)\n if (sharedText) {\n awareness.setLocalStateField(cmCursorFieldName, null)\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => { const n = Math.floor(v); return Number.isFinite(n) ? Math.max(0, n) : 0 }\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n"],"mappings":"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","names":["doc","value","import_y_prosemirror","import_prosemirror_state","import_y_prosemirror","defaultOnWarning","import_core"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/bridge.ts","../src/types.ts","../src/awareness-proxy.ts","../src/collab-plugins.ts","../src/bridge-sync-plugin.ts","../src/cursor-sync-plugin.ts"],"sourcesContent":["export { createYjsBridge, replaceSharedText, replaceSharedProseMirror } from './bridge.js'\nexport type { YjsBridgeOptions, ReplaceResult, ReplaceTextResult, ReplaceProseMirrorResult } from './bridge.js'\nexport { createAwarenessProxy } from './awareness-proxy.js'\nexport { createCollabPlugins } from './collab-plugins.js'\nexport type { CollabPluginsOptions, ProseMirrorMapping, YCursorPluginOpts, YUndoPluginOpts } from './collab-plugins.js'\nexport {\n ORIGIN_TEXT_TO_PM,\n ORIGIN_PM_TO_TEXT,\n ORIGIN_INIT,\n} from './types.js'\nexport type {\n BootstrapResult,\n YjsBridgeConfig,\n YjsBridgeHandle,\n WarningCode,\n WarningEvent,\n OnWarning,\n} from './types.js'\n\n// Cursor mapping re-exported from @pm-cm/core\nexport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nexport type { TextSegment, CursorMap, SerializeWithMap, CursorMapWriter, Matcher, MatchResult, MatchRun } from '@pm-cm/core'\n\n// Bridge sync plugin (auto PM→Y.Text wiring)\nexport { createBridgeSyncPlugin, bridgeSyncPluginKey } from './bridge-sync-plugin.js'\nexport type { BridgeSyncPluginOptions } from './bridge-sync-plugin.js'\n\n// Cursor sync plugin\nexport { createCursorSyncPlugin, cursorSyncPluginKey, syncCmCursor } from './cursor-sync-plugin.js'\nexport type { CursorSyncState, CursorSyncPluginOptions } from './cursor-sync-plugin.js'\n\n// Re-export types from @pm-cm/core\nexport { createCursorMapWriter, wrapSerialize } from '@pm-cm/core'\nexport type { Serialize, Parse, Normalize, OnError, ErrorCode, ErrorEvent } from '@pm-cm/core'\n","import type { Node } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport { prosemirrorToYXmlFragment, yXmlFragmentToProseMirrorRootNode, ySyncPluginKey } from 'y-prosemirror'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { Normalize, OnError } from '@pm-cm/core'\nimport type { BootstrapResult, YjsBridgeConfig, YjsBridgeHandle } from './types.js'\nimport { ORIGIN_INIT, ORIGIN_TEXT_TO_PM, ORIGIN_PM_TO_TEXT } from './types.js'\n\nconst defaultNormalize: Normalize = (s) => s.replace(/\\r\\n?/g, '\\n')\nconst defaultOnError: OnError = (event) => console.error(`[bridge] ${event.code}: ${event.message}`, event.cause)\n\n/**\n * Check whether any type modified in the transaction belongs to the\n * given XmlFragment subtree (the fragment itself or any descendant).\n *\n * Uses the Yjs internal `_item.parent` chain which has been stable\n * across the 13.x series and is also relied on by y-prosemirror.\n */\nfunction transactionTouchedXmlFragment(\n changed: Map<object, Set<string | null>>,\n xmlFragment: YXmlFragment,\n): boolean {\n for (const type of changed.keys()) {\n if (type === xmlFragment) return true\n // Walk the parent chain from the changed type up to the root.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let item = (type as any)._item\n while (item) {\n if (item.parent === xmlFragment) return true\n item = item.parent?._item ?? null\n }\n }\n return false\n}\n\n/** Result of {@link replaceSharedText}. */\nexport type ReplaceTextResult =\n | { ok: true }\n | { ok: false; reason: 'unchanged' }\n | { ok: false; reason: 'detached' }\n | { ok: false; reason: 'serialize-error' }\n | { ok: false; reason: 'skip-pending' }\n | { ok: false; reason: 'parse-failed' }\n\n/** Result of {@link replaceSharedProseMirror}. */\nexport type ReplaceProseMirrorResult =\n | { ok: true }\n | { ok: false; reason: 'parse-error' }\n | { ok: false; reason: 'detached' }\n\n/**\n * Union of all replace-result types. Kept for backward compatibility.\n * Prefer the narrower {@link ReplaceTextResult} / {@link ReplaceProseMirrorResult}.\n */\nexport type ReplaceResult = ReplaceTextResult | ReplaceProseMirrorResult\n\n/**\n * Replace `Y.Text` content using a minimal diff (common prefix/suffix trimming).\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedText(\n sharedText: YText,\n next: string,\n origin: unknown,\n normalize: Normalize = defaultNormalize,\n): ReplaceTextResult {\n if (!sharedText.doc) {\n return { ok: false, reason: 'detached' }\n }\n\n const normalized = normalize(next)\n const current = sharedText.toString()\n if (current === normalized) {\n return { ok: false, reason: 'unchanged' }\n }\n\n // Minimal diff: find common prefix and suffix, replace only the changed middle.\n let start = 0\n const minLen = Math.min(current.length, normalized.length)\n while (start < minLen && current.charCodeAt(start) === normalized.charCodeAt(start)) {\n start++\n }\n\n let endCurrent = current.length\n let endNext = normalized.length\n while (endCurrent > start && endNext > start && current.charCodeAt(endCurrent - 1) === normalized.charCodeAt(endNext - 1)) {\n endCurrent--\n endNext--\n }\n\n sharedText.doc.transact(() => {\n const deleteCount = endCurrent - start\n if (deleteCount > 0) {\n sharedText.delete(start, deleteCount)\n }\n const insertStr = normalized.slice(start, endNext)\n if (insertStr.length > 0) {\n sharedText.insert(start, insertStr)\n }\n }, origin)\n\n return { ok: true }\n}\n\n/**\n * Replace `Y.XmlFragment` by parsing serialized text into a ProseMirror document.\n * Returns a {@link ReplaceResult} indicating success or failure reason.\n */\nexport function replaceSharedProseMirror(\n doc: Doc,\n fragment: YXmlFragment,\n text: string,\n origin: unknown,\n config: Pick<YjsBridgeConfig, 'schema' | 'parse' | 'normalize' | 'onError'>,\n): ReplaceProseMirrorResult {\n if (!fragment.doc) {\n return { ok: false, reason: 'detached' }\n }\n if (fragment.doc !== doc) {\n throw new Error('fragment belongs to a different Y.Doc than the provided doc')\n }\n\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n let nextDoc: Node\n try {\n nextDoc = config.parse(normalize(text), config.schema)\n } catch (error) {\n onError({ code: 'parse-error', message: 'failed to parse text into ProseMirror document', cause: error })\n return { ok: false, reason: 'parse-error' }\n }\n\n doc.transact(() => {\n prosemirrorToYXmlFragment(nextDoc, fragment)\n }, origin)\n return { ok: true }\n}\n\n/** Options for {@link createYjsBridge}. */\nexport type YjsBridgeOptions = {\n initialText?: string\n /** Which side wins when both sharedText and sharedProseMirror exist and differ. Default `'text'`. */\n prefer?: 'text' | 'prosemirror'\n}\n\n/**\n * Create a collaborative bridge that keeps `Y.Text` and `Y.XmlFragment` in sync.\n *\n * Runs a synchronous bootstrap to reconcile existing state, then installs a\n * `Y.Text` observer for the text → ProseMirror direction.\n *\n * @throws If `sharedText` or `sharedProseMirror` belong to a different `Y.Doc`.\n */\nexport function createYjsBridge(\n config: YjsBridgeConfig,\n options?: YjsBridgeOptions,\n): YjsBridgeHandle {\n const {\n doc,\n sharedText,\n sharedProseMirror,\n schema,\n serialize,\n parse,\n } = config\n const normalize = config.normalize ?? defaultNormalize\n const onError = config.onError ?? defaultOnError\n const skipOrigins = config.skipOrigins ?? null\n\n if (!sharedText.doc) {\n throw new Error('sharedText is not attached to any Y.Doc')\n }\n if (sharedText.doc !== doc) {\n throw new Error('sharedText belongs to a different Y.Doc than the provided doc')\n }\n if (!sharedProseMirror.doc) {\n throw new Error('sharedProseMirror is not attached to any Y.Doc')\n }\n if (sharedProseMirror.doc !== doc) {\n throw new Error('sharedProseMirror belongs to a different Y.Doc than the provided doc')\n }\n\n let lastBridgedText: string | null = null\n\n // Manages the deferred fallback for skipOrigins: an XmlFragment observer\n // detects when the expected remote update arrives (cancelling the fallback),\n // and a timeout fires the fallback only if the update never arrives.\n let pendingSkipCleanup: (() => void) | null = null\n\n // When true, syncToSharedText is blocked. Set during the skip window\n // (between a skipOrigins Y.Text change and the paired XmlFragment update)\n // to prevent stale PM content from overwriting newer Y.Text.\n let skipPending = false\n\n // When true, syncToSharedText is blocked because the last\n // syncTextToProsemirror failed to parse. PM is stale and must not\n // overwrite newer Y.Text content. Cleared on the next successful parse\n // or when a transaction touches both Y.Text and XmlFragment (ySyncPlugin\n // handles the XmlFragment → PM direction in that case).\n let parseFailed = false\n\n /** Returns `true` if the parse succeeded. */\n const syncTextToProsemirror = (origin: unknown): boolean => {\n const text = normalize(sharedText.toString())\n if (lastBridgedText === text) {\n return true\n }\n\n const result = replaceSharedProseMirror(doc, sharedProseMirror, text, origin, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (result.ok) {\n parseFailed = false\n // Canonicalize only during bootstrap (ORIGIN_INIT).\n //\n // During live editing the text may be in an intermediate state where\n // serialize(parse(text)) !== text (e.g. partially typed syntax that the\n // parser interprets differently from the user's intent). Writing the\n // canonical form back to Y.Text in that situation corrupts the document\n // and can trigger reentrant updates in editor view-plugins that observe\n // Y.Text changes.\n //\n // At bootstrap time the text is well-formed (loaded from persistence),\n // so the round-trip is idempotent and canonicalization ensures both\n // shared types agree for future bridge mounts (`both-match`).\n let canonical = text\n if (origin === ORIGIN_INIT) {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n canonical = normalize(serialize(pmDoc))\n if (canonical !== text) {\n replaceSharedText(sharedText, canonical, ORIGIN_INIT, normalize)\n }\n } catch {\n // Serialization failure during canonicalization is non-fatal;\n // fall back to using the original text as lastBridgedText.\n }\n }\n lastBridgedText = canonical\n } else {\n parseFailed = true\n // Parse failed — still update lastBridgedText to prevent the\n // textObserver from re-attempting the same failed parse on every\n // Y.Text change. PM→Y.Text writes are blocked by the parseFailed\n // flag until the next successful syncTextToProsemirror.\n lastBridgedText = text\n }\n return result.ok\n }\n\n const sharedProseMirrorToText = (fragment: YXmlFragment): string | null => {\n try {\n const pmDoc = yXmlFragmentToProseMirrorRootNode(fragment, schema)\n return normalize(serialize(pmDoc))\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to convert ProseMirror fragment to text', cause: error })\n return null\n }\n }\n\n // Bootstrap\n const bootstrap = (): BootstrapResult => {\n const text = normalize(sharedText.toString())\n const hasText = text.length > 0\n const hasProsemirror = sharedProseMirror.length > 0\n\n if (!hasText && !hasProsemirror) {\n const initial = options?.initialText ?? ''\n if (initial.length > 0) {\n // Set Y.XmlFragment first, then derive Y.Text from serialize(parse(initial))\n // to ensure both shared types are in the same canonical form.\n const initResult = replaceSharedProseMirror(doc, sharedProseMirror, initial, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!initResult.ok) {\n return { source: 'initial', parseError: true }\n }\n const pmDoc = yXmlFragmentToProseMirrorRootNode(sharedProseMirror, schema)\n let canonicalText: string\n try {\n canonicalText = serialize(pmDoc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document during bootstrap', cause: error })\n return { source: 'initial', parseError: true }\n }\n replaceSharedText(sharedText, canonicalText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(canonicalText)\n return { source: 'initial' }\n }\n return { source: 'empty' }\n }\n\n if (hasText && !hasProsemirror) {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n\n if (!hasText && hasProsemirror) {\n const textFromProsemirror = sharedProseMirrorToText(sharedProseMirror)\n if (textFromProsemirror !== null) {\n replaceSharedText(sharedText, textFromProsemirror, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(textFromProsemirror)\n return { source: 'prosemirror' }\n }\n return { source: 'prosemirror', parseError: true }\n }\n\n const prosemirrorText = sharedProseMirrorToText(sharedProseMirror)\n if (prosemirrorText === null) {\n const fallbackText = hasText ? text : (options?.initialText ?? '')\n let parseError = false\n if (fallbackText.length > 0) {\n replaceSharedText(sharedText, fallbackText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(fallbackText)\n const fallbackResult = replaceSharedProseMirror(doc, sharedProseMirror, fallbackText, ORIGIN_INIT, {\n schema,\n parse,\n normalize,\n onError,\n })\n if (!fallbackResult.ok) {\n parseError = true\n // Block PM→Y.Text writes — PM is stale (XmlFragment wasn't\n // updated because parse failed). Without this, a subsequent\n // PM edit can overwrite the authoritative Y.Text content.\n parseFailed = true\n }\n }\n return { source: 'text', ...(parseError && { parseError: true }) }\n }\n\n if (prosemirrorText !== text) {\n const prefer = options?.prefer ?? 'text'\n if (prefer === 'prosemirror') {\n replaceSharedText(sharedText, prosemirrorText, ORIGIN_INIT, normalize)\n lastBridgedText = normalize(prosemirrorText)\n return { source: 'prosemirror' }\n } else {\n const ok = syncTextToProsemirror(ORIGIN_INIT)\n return { source: 'text', ...(!ok && { parseError: true }) }\n }\n } else {\n lastBridgedText = text\n return { source: 'both-match' }\n }\n }\n\n const textObserver = (\n _: unknown,\n transaction: { origin: unknown; changed: Map<object, Set<string | null>> },\n ) => {\n if (transaction.origin === ORIGIN_PM_TO_TEXT || transaction.origin === ORIGIN_INIT) {\n return\n }\n\n // When the same transaction also modified the XmlFragment subtree\n // (e.g. a remote Y.Doc update applied via Y.applyUpdate that modified\n // both shared types atomically), ySyncPlugin already handles the\n // XmlFragment → PM direction. Calling syncTextToProsemirror would\n // redundantly reconstruct the XmlFragment via prosemirrorToYXmlFragment,\n // destroying existing Yjs Item IDs and invalidating cursor\n // RelativePositions held by peers in Awareness state.\n if (transactionTouchedXmlFragment(transaction.changed, sharedProseMirror)) {\n lastBridgedText = normalize(sharedText.toString())\n // XmlFragment was also updated in this transaction — ySyncPlugin\n // handles the XmlFragment → PM direction, so PM is up to date.\n // Clear parseFailed since PM is no longer stale.\n parseFailed = false\n return\n }\n\n // When multiple clients run a bridge, a remote Y.Text change and its\n // corresponding Y.XmlFragment change arrive as separate transactions.\n // Writing to XmlFragment here would race with the remote XmlFragment\n // update, causing the CRDT to keep both insertions (duplicate nodes).\n // Skip the sync and let the remote XmlFragment update handle it.\n //\n // Event-driven fallback: install a one-shot XmlFragment observer to\n // detect when the expected update arrives (cancelling the fallback).\n // A timeout fires the fallback only if no XmlFragment change arrives,\n // preventing PM from becoming permanently stale when the sender is\n // a text-only producer or the XmlFragment update was lost.\n if (skipOrigins !== null && skipOrigins.has(transaction.origin)) {\n const expectedText = normalize(sharedText.toString())\n lastBridgedText = expectedText\n\n // If XmlFragment already matches the expected text (e.g., the paired\n // XmlFragment update arrived before the Y.Text update), no skip\n // window is needed — XmlFragment is already caught up.\n const pmTextNow = sharedProseMirrorToText(sharedProseMirror)\n if (pmTextNow !== null && normalize(pmTextNow) === expectedText) {\n parseFailed = false\n // Clean up any previous pending skip fallback so its skipPending\n // doesn't remain latched after this early return.\n if (pendingSkipCleanup) pendingSkipCleanup()\n return\n }\n\n // Snapshot XmlFragment state at skip start. The fallback uses this\n // to detect whether XmlFragment was modified during the skip window\n // (by ySyncPlugin or local PM edits). If modified, the fallback\n // must NOT overwrite XmlFragment with older Y.Text content.\n const xmlTextAtSkipStart = pmTextNow\n\n // Cancel any previous pending skip fallback BEFORE setting skipPending,\n // because the previous cleanup sets skipPending = false.\n if (pendingSkipCleanup) pendingSkipCleanup()\n skipPending = true\n\n let resolved = false\n\n const resolve = () => {\n if (resolved) return\n resolved = true\n skipPending = false\n sharedProseMirror.unobserveDeep(xmlCatchUpObserver)\n clearTimeout(timer)\n pendingSkipCleanup = null\n }\n\n // Observe deep XmlFragment changes to detect when the expected\n // remote update arrives. Requires BOTH origin match AND content\n // verification: origin alone is coarse (shared across peers) and\n // could match an unrelated update, prematurely unblocking stale\n // PM→Y.Text writes. Content verification ensures the XmlFragment\n // has actually caught up to the expected text. When parse/serialize\n // is non-idempotent (content never matches), the timeout fallback\n // handles it.\n const xmlCatchUpObserver = (\n _events: unknown[],\n transaction: { origin: unknown },\n ) => {\n if (resolved) return\n if (skipOrigins!.has(transaction.origin)) {\n const pmText = sharedProseMirrorToText(sharedProseMirror)\n if (pmText !== null && normalize(pmText) === expectedText) {\n // XmlFragment caught up — ySyncPlugin updates PM, so\n // PM is no longer stale from any prior parse failure.\n parseFailed = false\n resolve()\n }\n }\n }\n\n const runFallback = () => {\n resolve()\n // Guard: if text has changed since, a newer update superseded this one.\n if (lastBridgedText !== expectedText) return\n // Verify XmlFragment caught up by comparing its serialized form.\n const pmText = sharedProseMirrorToText(sharedProseMirror)\n if (pmText === null || normalize(pmText) !== expectedText) {\n // If XmlFragment was modified during the skip window (by\n // ySyncPlugin applying remote updates or by local PM edits),\n // the current state is authoritative — do NOT overwrite with\n // older Y.Text content. Update lastBridgedText and let the\n // normal bridge flow reconcile via syncToSharedText.\n //\n // Three cases detect modification:\n // 1. XmlFragment content differs from the snapshot at skip start.\n // 2. XmlFragment was unserializable at skip start but is now\n // serializable — something changed its structure.\n // 3. XmlFragment was serializable at skip start but is now\n // unserializable — structure was modified in a breaking way.\n const changedDuringSkip =\n (xmlTextAtSkipStart !== null && pmText !== null && normalize(pmText) !== normalize(xmlTextAtSkipStart))\n || (xmlTextAtSkipStart === null && pmText !== null)\n || (xmlTextAtSkipStart !== null && pmText === null)\n if (changedDuringSkip) {\n lastBridgedText = pmText !== null ? normalize(pmText) : lastBridgedText\n // Do NOT clear parseFailed here. XmlFragment changed but\n // hasn't caught up to expectedText — PM may be at an\n // intermediate state. Keeping parseFailed blocks stale\n // PM→Y.Text writes until a new Y.Text change triggers\n // a successful syncTextToProsemirror.\n return\n }\n // XmlFragment unchanged since skip start — truly stale\n // (paired XmlFragment never arrived). Sync Y.Text → XmlFragment.\n lastBridgedText = null\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n }\n\n const timer = setTimeout(runFallback, 500)\n\n sharedProseMirror.observeDeep(xmlCatchUpObserver)\n pendingSkipCleanup = resolve\n return\n }\n\n syncTextToProsemirror(ORIGIN_TEXT_TO_PM)\n }\n\n // Run bootstrap synchronously before installing the observer so that\n // an exception during bootstrap cannot leave a dangling observer.\n const bootstrapResult = bootstrap()\n\n sharedText.observe(textObserver)\n\n return {\n bootstrapResult,\n syncToSharedText(doc: Node): ReplaceTextResult {\n // Block PM→Y.Text writes during the skip window to prevent stale\n // PM content from overwriting newer Y.Text. The skip is resolved\n // when the paired XmlFragment update arrives (observer) or by the\n // timeout fallback.\n if (skipPending) {\n return { ok: false, reason: 'skip-pending' }\n }\n // Block PM→Y.Text writes when the last Y.Text→PM parse failed.\n // PM is stale and writing it back would resurrect deleted content.\n // Cleared on the next successful syncTextToProsemirror.\n if (parseFailed) {\n return { ok: false, reason: 'parse-failed' }\n }\n let text: string\n try {\n text = serialize(doc)\n } catch (error) {\n onError({ code: 'serialize-error', message: 'failed to serialize ProseMirror document', cause: error })\n return { ok: false, reason: 'serialize-error' }\n }\n const result = replaceSharedText(sharedText, text, ORIGIN_PM_TO_TEXT, normalize)\n // Always update lastBridgedText unless truly failed (detached).\n // 'unchanged' means Y.Text already has this content — still need to\n // record it so the reverse observer doesn't trigger a redundant sync.\n if (result.ok || result.reason === 'unchanged') {\n lastBridgedText = normalize(text)\n }\n return result\n },\n isYjsSyncChange(tr: Transaction): boolean {\n // Internal meta shape from y-prosemirror's ySyncPlugin (tested against ^1.3.x).\n const meta = tr.getMeta(ySyncPluginKey)\n return (\n typeof meta === 'object' &&\n meta !== null &&\n 'isChangeOrigin' in meta &&\n (meta as Record<string, unknown>).isChangeOrigin === true\n )\n },\n dispose() {\n sharedText.unobserve(textObserver)\n if (pendingSkipCleanup) pendingSkipCleanup()\n },\n }\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { Transaction } from 'prosemirror-state'\nimport type { Serialize, Parse, Normalize, OnError } from '@pm-cm/core'\nimport type { Doc, Text as YText, XmlFragment as YXmlFragment } from 'yjs'\nimport type { ReplaceTextResult } from './bridge.js'\n\n/** Known warning codes emitted by the yjs bridge and plugins. */\nexport type WarningCode = 'bridge-already-wired' | 'sync-failed' | 'ysync-plugin-missing' | 'cursor-sync-not-installed' | 'cursor-map-error'\n\n/** Structured warning event for non-fatal warnings. */\nexport type WarningEvent = {\n code: WarningCode\n message: string\n}\n\n/**\n * Warning handler callback for non-fatal warnings.\n *\n * Known codes:\n * - `'bridge-already-wired'` — the same bridge handle is wired to multiple plugin instances.\n * - `'sync-failed'` — `syncToSharedText` failed (e.g. Y.Text detached).\n * - `'ysync-plugin-missing'` — ySyncPlugin state is not available; cursor broadcast skipped.\n * - `'cursor-sync-not-installed'` — cursor sync plugin is not installed on the EditorView.\n * - `'cursor-map-error'` — failed to build cursor map (serialize threw); cursor sync skipped.\n */\nexport type OnWarning = (event: WarningEvent) => void\n\n/** Yjs transaction origin: text → ProseMirror direction. */\nexport const ORIGIN_TEXT_TO_PM = 'bridge:text-to-prosemirror'\n\n/** Yjs transaction origin: ProseMirror → text direction. */\nexport const ORIGIN_PM_TO_TEXT = 'bridge:prosemirror-to-text'\n\n/** Yjs transaction origin: bootstrap initialization. */\nexport const ORIGIN_INIT = 'bridge:init'\n\n/** Configuration for {@link createYjsBridge}. */\nexport type YjsBridgeConfig = {\n doc: Doc\n sharedText: YText\n sharedProseMirror: YXmlFragment\n schema: Schema\n serialize: Serialize\n parse: Parse\n normalize?: Normalize\n /** Called on non-fatal errors (e.g. parse failures). Defaults to `console.error`. */\n onError?: OnError\n /**\n * Set of Y.Doc transaction origins for which the bridge should NOT run\n * text → ProseMirror sync.\n *\n * **When to use:** When multiple clients each run a bridge, a remote\n * Y.Text change and its corresponding Y.XmlFragment change may arrive\n * as separate transactions (they originate from separate `doc.transact`\n * calls and travel as separate provider messages). Without this option\n * the receiving bridge writes to XmlFragment for the Y.Text-only\n * transaction, and the subsequent CRDT merge with the remote\n * XmlFragment update creates duplicate nodes (especially empty\n * paragraphs whose content is identical).\n *\n * **Important:** Only include origins where every Y.Text change is\n * guaranteed to be accompanied by a corresponding Y.XmlFragment update\n * (i.e. the sender also runs a bridge). Do **not** include origins used\n * by text-only producers (e.g. a CodeMirror-only peer without a bridge),\n * or ProseMirror will miss those changes and become stale.\n *\n * @example\n * ```ts\n * // All peers run a bridge — safe to skip remote origins\n * skipOrigins: new Set(['remote'])\n * ```\n */\n skipOrigins?: Set<unknown>\n}\n\n/**\n * Result of the bootstrap phase in {@link createYjsBridge}.\n * Indicates which source was used to initialize the shared types.\n */\nexport type BootstrapResult = {\n source: 'text' | 'prosemirror' | 'both-match' | 'empty' | 'initial'\n /** `true` when format conversion (parse or serialize) failed during bootstrap. The bridge is still usable but the affected shared type may be stale. */\n parseError?: boolean\n}\n\n/** Handle returned by {@link createYjsBridge}. */\nexport type YjsBridgeHandle = {\n /** Result of the synchronous bootstrap phase. */\n readonly bootstrapResult: BootstrapResult\n /** Serialize `doc` and push to `Y.Text` using minimal diff. */\n syncToSharedText(doc: Node): ReplaceTextResult\n /** Returns `true` if the transaction originated from `y-prosemirror` sync. */\n isYjsSyncChange(tr: Transaction): boolean\n /** Remove the Y.Text observer. Call when tearing down. */\n dispose(): void\n}\n","import type { Awareness } from 'y-protocols/awareness'\n\n/**\n * Create a Proxy around a Yjs {@link Awareness} that adapts the cursor sync\n * plugin's awareness field for y-prosemirror's `yCursorPlugin`.\n *\n * y-prosemirror (npm 1.3.7) hardcodes `\"cursor\"` in `createDecorations`,\n * but the cursor sync plugin writes to a separate field (default `\"pmCursor\"`)\n * to avoid conflicts with y-codemirror.next's `\"cursor\"` (Y.Text-based).\n *\n * This proxy:\n * - **`getStates()`**: remaps `cursorField` → `\"cursor\"` so yCursorPlugin\n * finds the PM cursor data under the hardcoded `\"cursor\"` key.\n * - **`getLocalState()`**: returns `cursor: null` so yCursorPlugin's\n * `updateCursorInfo` never tries to broadcast its own cursor.\n * - **`setLocalStateField(\"cursor\", …)`**: suppressed (no-op) so\n * yCursorPlugin cannot overwrite the field managed by the sync plugin.\n */\nexport function createAwarenessProxy(awareness: Awareness, cursorField = 'pmCursor'): Awareness {\n return new Proxy(awareness, {\n get(target, prop, receiver) {\n if (prop === 'getLocalState') {\n return () => {\n const state = target.getLocalState()\n // Hide \"cursor\" so yCursorPlugin's updateCursorInfo sees null\n return state ? { ...state, cursor: null } : state\n }\n }\n if (prop === 'setLocalStateField') {\n return (field: string, value: unknown) => {\n // Block yCursorPlugin's writes to \"cursor\"\n if (field === 'cursor') return\n target.setLocalStateField(field, value)\n }\n }\n if (prop === 'getStates') {\n return () => {\n const states = target.getStates()\n // Remap cursorField → \"cursor\" so yCursorPlugin reads PM cursor data.\n // Only override when the client actually has cursorField — clients\n // without cursor sync (e.g. using yCursorPlugin directly) keep their\n // original \"cursor\" intact.\n const remapped = new Map<number, Record<string, unknown>>()\n states.forEach((state, clientId) => {\n const s = state as Record<string, unknown>\n if (cursorField in s) {\n remapped.set(clientId, { ...s, cursor: s[cursorField] ?? null })\n } else {\n remapped.set(clientId, s)\n }\n })\n return remapped\n }\n }\n const value = Reflect.get(target, prop, receiver) as unknown\n return typeof value === 'function' ? (value as Function).bind(target) : value\n },\n }) as Awareness\n}\n","import type { Node, Schema } from 'prosemirror-model'\nimport type { EditorState, Plugin } from 'prosemirror-state'\nimport type { DecorationAttrs } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport type { Serialize, SerializeWithMap } from '@pm-cm/core'\nimport { initProseMirrorDoc, yCursorPlugin, ySyncPlugin, yUndoPlugin } from 'y-prosemirror'\nimport type { AbstractType, Text as YText, UndoManager } from 'yjs'\nimport type { XmlFragment as YXmlFragment } from 'yjs'\nimport { createAwarenessProxy } from './awareness-proxy.js'\nimport { createBridgeSyncPlugin } from './bridge-sync-plugin.js'\nimport { createCursorSyncPlugin } from './cursor-sync-plugin.js'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\n/** Yjs ↔ ProseMirror node mapping used by `y-prosemirror`. */\nexport type ProseMirrorMapping = Map<AbstractType<unknown>, Node | Node[]>\n\n/** Options forwarded to `yCursorPlugin` from y-prosemirror. */\nexport type YCursorPluginOpts = {\n awarenessStateFilter?: (currentClientId: number, userClientId: number, user: unknown) => boolean\n cursorBuilder?: (user: unknown, clientId: number) => HTMLElement\n selectionBuilder?: (user: unknown, clientId: number) => DecorationAttrs\n getSelection?: (state: EditorState) => unknown\n}\n\n/** Options forwarded to `yUndoPlugin` from y-prosemirror. */\nexport type YUndoPluginOpts = {\n protectedNodes?: Set<string>\n trackedOrigins?: unknown[]\n undoManager?: UndoManager | null\n}\n\n/** Options for {@link createCollabPlugins}. */\nexport type CollabPluginsOptions = {\n /** Shared ProseMirror document in Yjs. */\n sharedProseMirror: YXmlFragment\n awareness: Awareness\n cursorFieldName?: string\n serialize?: Serialize | SerializeWithMap\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * Enable PM↔CM cursor sync. Default `false`.\n *\n * When enabled, an {@link createAwarenessProxy | awareness proxy} is applied\n * to suppress y-prosemirror's built-in cursor management.\n */\n cursorSync?: boolean\n /**\n * The shared `Y.Text` instance. When provided, the cursor sync plugin also\n * broadcasts CM-format cursor positions so remote `yCollab` instances render them.\n */\n sharedText?: YText\n /**\n * When provided, a bridge sync plugin is inserted before the cursor sync plugin\n * to ensure Y.Text is synced before cursor positions are computed. This guarantees\n * that serialize-based offsets match Y.Text indices.\n */\n bridge?: YjsBridgeHandle\n /** Extra options forwarded to `yCursorPlugin`. */\n yCursorPluginOpts?: YCursorPluginOpts\n /** Extra options forwarded to `yUndoPlugin`. */\n yUndoPluginOpts?: YUndoPluginOpts\n /**\n * When `true`, the bridge sync plugin calls `bridge.dispose()` when\n * the last plugin instance for this bridge is destroyed. Default `false`.\n *\n * Enable this only when the factory owns the bridge lifecycle.\n * When the caller creates and manages the bridge separately, leave\n * this `false` and call `bridge.dispose()` manually.\n */\n autoDisposeBridge?: boolean\n /** Called for non-fatal warnings. Propagated to child plugins. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * Bundle `ySyncPlugin`, `yCursorPlugin`, `yUndoPlugin` from y-prosemirror,\n * plus an optional PM↔CM cursor sync plugin.\n *\n * @throws If `cursorSync: true` but `serialize` is not provided.\n */\nexport function createCollabPlugins(\n schema: Schema,\n options: CollabPluginsOptions,\n): { plugins: Plugin[]; doc: Node; mapping: ProseMirrorMapping } {\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const enableCursorSync = options.cursorSync ?? false\n const { sharedProseMirror } = options\n\n if (enableCursorSync && !options.serialize) {\n throw new Error('createCollabPlugins: cursorSync requires serialize to be provided')\n }\n const { doc, mapping: rawMapping } = initProseMirrorDoc(sharedProseMirror, schema)\n const mapping = rawMapping as ProseMirrorMapping\n const pmAwareness = enableCursorSync\n ? createAwarenessProxy(options.awareness, cursorFieldName)\n : options.awareness\n\n const plugins: Plugin[] = [\n ySyncPlugin(sharedProseMirror, { mapping: rawMapping }),\n yCursorPlugin(pmAwareness, options.yCursorPluginOpts ?? {}),\n yUndoPlugin(options.yUndoPluginOpts),\n ]\n\n // Bridge sync plugin must run before cursor sync plugin so that\n // Y.Text is updated before cursor positions are computed.\n if (options.bridge) {\n plugins.push(createBridgeSyncPlugin(options.bridge, {\n onWarning: options.onWarning,\n autoDispose: options.autoDisposeBridge,\n }))\n }\n\n if (enableCursorSync && options.serialize) {\n plugins.push(\n createCursorSyncPlugin({\n awareness: options.awareness,\n serialize: options.serialize,\n cursorFieldName,\n cmCursorFieldName: options.cmCursorFieldName,\n sharedText: options.sharedText,\n onWarning: options.onWarning,\n }),\n )\n }\n\n return { plugins, doc, mapping }\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { EditorView } from 'prosemirror-view'\nimport type { YjsBridgeHandle, OnWarning } from './types.js'\n\ntype BridgeSyncState = { needsSync: boolean }\n\ntype BridgeSyncFailure = { ok: false; reason: 'detached' | 'serialize-error' | 'parse-failed' }\n\n/** Options for {@link createBridgeSyncPlugin}. */\nexport type BridgeSyncPluginOptions = {\n /** Called when `syncToSharedText` fails (excludes `reason: 'unchanged'`). */\n onSyncFailure?: (result: BridgeSyncFailure, view: EditorView) => void\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n /**\n * When `true`, automatically call `bridge.dispose()` when the last\n * plugin instance for this bridge is destroyed. Default `false`.\n */\n autoDispose?: boolean\n}\n\n/** ProseMirror plugin key for {@link createBridgeSyncPlugin}. Use to read the plugin state. */\nexport const bridgeSyncPluginKey = new PluginKey<BridgeSyncState>('pm-cm-bridge-sync')\n\nconst wiredBridges = new WeakMap<YjsBridgeHandle, number>()\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\n/**\n * ProseMirror plugin that automatically syncs PM doc changes to Y.Text\n * via the bridge handle. Skips Yjs-originated changes to avoid loops.\n *\n * A warning is logged if the same bridge handle is wired more than once.\n * The guard is cleaned up when the plugin is destroyed.\n */\nexport function createBridgeSyncPlugin(\n bridge: YjsBridgeHandle,\n options: BridgeSyncPluginOptions = {},\n): Plugin {\n const warn = options.onWarning ?? defaultOnWarning\n\n // Closure flag consumed in view.update — avoids sticky plugin state across dispatches.\n let needsSync = false\n\n return new Plugin<BridgeSyncState>({\n key: bridgeSyncPluginKey,\n\n state: {\n init(): BridgeSyncState {\n return { needsSync: false }\n },\n apply(tr, _prev): BridgeSyncState {\n if (!tr.docChanged) return { needsSync }\n // Skip the direct Yjs-originated transaction (the ySyncPlugin change\n // itself). Follow-up appendTransactions (e.g. prosemirror-tables\n // normalization) ARE allowed to set needsSync — if they materially\n // change the doc, the resulting text must reach Y.Text.\n // replaceSharedText's 'unchanged' check prevents redundant writes\n // when the appended change does not affect the serialized output.\n if (bridge.isYjsSyncChange(tr)) {\n return { needsSync }\n }\n needsSync = true\n return { needsSync: true }\n },\n },\n\n view() {\n const count = wiredBridges.get(bridge) ?? 0\n if (count > 0) {\n warn({ code: 'bridge-already-wired', message: 'this bridge is already wired to another plugin instance' })\n }\n wiredBridges.set(bridge, count + 1)\n\n return {\n update(view) {\n if (needsSync) {\n const result = bridge.syncToSharedText(view.state.doc)\n if (!result.ok) {\n // skip-pending / parse-failed: preserve needsSync so the edit\n // is retried on the next view update after the condition resolves.\n // parse-failed is reported to the caller unlike skip-pending.\n if (result.reason === 'skip-pending') {\n return\n }\n if (result.reason === 'parse-failed') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n return\n }\n if (result.reason !== 'unchanged') {\n options.onSyncFailure?.(result, view)\n warn({ code: 'sync-failed', message: `bridge sync failed: ${result.reason}` })\n }\n }\n }\n needsSync = false\n },\n destroy() {\n const remaining = (wiredBridges.get(bridge) ?? 1) - 1\n if (remaining <= 0) {\n wiredBridges.delete(bridge)\n if (options.autoDispose) bridge.dispose()\n } else {\n wiredBridges.set(bridge, remaining)\n }\n },\n }\n },\n })\n}\n","import { Plugin, PluginKey } from 'prosemirror-state'\nimport type { Node } from 'prosemirror-model'\nimport type { EditorView } from 'prosemirror-view'\nimport type { Awareness } from 'y-protocols/awareness'\nimport { absolutePositionToRelativePosition, ySyncPluginKey } from 'y-prosemirror'\nimport { createRelativePositionFromTypeIndex, createAbsolutePositionFromRelativePosition } from 'yjs'\nimport type { Text as YText, XmlFragment as YXmlFragment, RelativePosition } from 'yjs'\nimport type { Serialize, SerializeWithMap, CursorMap } from '@pm-cm/core'\nimport { buildCursorMap, cursorMapLookup, reverseCursorMapLookup } from '@pm-cm/core'\nimport type { OnWarning } from './types.js'\n\n/** Plugin state for the cursor sync plugin. Read via {@link cursorSyncPluginKey}. */\nexport type CursorSyncState = {\n /** Pending CodeMirror cursor to broadcast. Set by {@link syncCmCursor}. */\n pendingCm: { anchor: number; head: number } | null\n /** Text offset mapped from the current PM selection anchor. `null` when no mapping is available. */\n mappedTextOffset: number | null\n}\n\n/** ProseMirror plugin key for {@link createCursorSyncPlugin}. Use to read the plugin state. */\nexport const cursorSyncPluginKey = new PluginKey<CursorSyncState>('pm-cm-cursor-sync')\n\n/**\n * Internal shape of `ySyncPluginKey` state from y-prosemirror.\n * Not exported by upstream — kept here for explicit tracking.\n * Tested against y-prosemirror ^1.3.x.\n */\ntype YSyncPluginState = { type: YXmlFragment; binding: { mapping: Map<unknown, unknown> } }\n\nfunction getYSyncState(view: EditorView): YSyncPluginState | null {\n const raw = ySyncPluginKey.getState(view.state) as Record<string, unknown> | undefined\n if (!raw) return null\n if (\n typeof raw === 'object' &&\n 'type' in raw && raw.type &&\n 'binding' in raw && raw.binding &&\n typeof raw.binding === 'object' &&\n 'mapping' in (raw.binding as Record<string, unknown>) &&\n (raw.binding as Record<string, unknown>).mapping instanceof Map\n ) {\n return raw as unknown as YSyncPluginState\n }\n return null\n}\n\nfunction toRelativePosition(\n view: EditorView,\n pmPos: number,\n): unknown | null {\n const ySyncState = getYSyncState(view)\n if (!ySyncState) return null\n\n return absolutePositionToRelativePosition(\n pmPos,\n ySyncState.type,\n ySyncState.binding.mapping as any, // eslint-disable-line @typescript-eslint/no-explicit-any -- y-prosemirror internal mapping type\n )\n}\n\n/** Returns `false` when ySyncPlugin state is unavailable (plugin not installed). */\nfunction broadcastPmCursor(\n awareness: Awareness,\n cursorFieldName: string,\n view: EditorView,\n pmAnchor: number,\n pmHead: number,\n): boolean {\n const relAnchor = toRelativePosition(view, pmAnchor)\n const relHead = toRelativePosition(view, pmHead)\n if (relAnchor === null || relHead === null) return false\n\n awareness.setLocalStateField(cursorFieldName, { anchor: relAnchor, head: relHead })\n return true\n}\n\nfunction broadcastTextCursor(\n awareness: Awareness,\n cmCursorFieldName: string,\n sharedText: YText,\n textAnchor: number,\n textHead: number,\n): void {\n const len = sharedText.length\n const clamp = (v: number) => Math.max(0, Math.min(v, len))\n const relAnchor = createRelativePositionFromTypeIndex(sharedText, clamp(textAnchor))\n const relHead = createRelativePositionFromTypeIndex(sharedText, clamp(textHead))\n awareness.setLocalStateField(cmCursorFieldName, { anchor: relAnchor, head: relHead })\n}\n\nconst defaultOnWarning: OnWarning = (event) => console.warn(`[pm-cm] ${event.code}: ${event.message}`)\n\nconst awarenessRefCounts = new WeakMap<Awareness, number>()\nconst awarenessFieldNames = new WeakMap<Awareness, Set<string>>()\n\n/** Options for {@link createCursorSyncPlugin}. */\nexport type CursorSyncPluginOptions = {\n awareness: Awareness\n serialize: Serialize | SerializeWithMap\n cursorFieldName?: string\n /** Awareness field used for CM/Y.Text cursor payloads. Default `'cursor'`. */\n cmCursorFieldName?: string\n /**\n * When provided, the plugin also broadcasts CM-format cursor positions\n * (Y.Text relative positions) to the awareness field specified by\n * `cmCursorFieldName`, so that remote `yCollab` instances can render the cursor.\n */\n sharedText?: YText\n /** Called for non-fatal warnings. Default `console.warn`. */\n onWarning?: OnWarning\n}\n\n/**\n * ProseMirror plugin that synchronizes cursor positions between PM and CM via Yjs awareness.\n *\n * - PM → awareness: automatically broadcasts when the PM view is focused and selection changes.\n * - CM → awareness: triggered by dispatching {@link syncCmCursor}.\n */\nexport function createCursorSyncPlugin(options: CursorSyncPluginOptions): Plugin {\n const { awareness, serialize, sharedText } = options\n const warn = options.onWarning ?? defaultOnWarning\n const cursorFieldName = options.cursorFieldName ?? 'pmCursor'\n const cmCursorFieldName = options.cmCursorFieldName ?? 'cursor'\n\n if (sharedText && cursorFieldName === cmCursorFieldName) {\n throw new Error(\n `createCursorSyncPlugin: cursorFieldName and cmCursorFieldName must differ when sharedText is provided (both are \"${cursorFieldName}\")`,\n )\n }\n\n let warnedSyncPluginMissing = false\n\n // Closure variable consumed in view.update — survives appendTransaction\n // that would otherwise clear the plugin state before update() runs.\n let pendingCmCursor: { anchor: number; head: number } | null = null\n\n // Cached cursor map (serialize-based) — rebuilt only when doc changes\n let cachedMap: CursorMap | null = null\n let cachedMapDoc: Node | null = null\n\n function getOrBuildMap(doc: Node): CursorMap | null {\n if (cachedMapDoc !== doc || !cachedMap) {\n try {\n cachedMap = buildCursorMap(doc, serialize)\n } catch (error) {\n warn({ code: 'cursor-map-error', message: 'failed to build cursor map — cursor sync skipped' })\n cachedMap = null\n }\n cachedMapDoc = doc\n }\n return cachedMap\n }\n\n return new Plugin<CursorSyncState>({\n key: cursorSyncPluginKey,\n\n state: {\n init(): CursorSyncState {\n return { pendingCm: null, mappedTextOffset: null }\n },\n apply(tr, prev, _oldState, newState): CursorSyncState {\n const cmMeta = tr.getMeta(cursorSyncPluginKey) as\n | { anchor: number; head: number }\n | undefined\n if (cmMeta) {\n pendingCmCursor = cmMeta\n }\n\n // Compute PM → text offset when selection or doc changes\n let mappedTextOffset = prev.mappedTextOffset\n if (tr.selectionSet || tr.docChanged) {\n const map = getOrBuildMap(newState.doc)\n mappedTextOffset = map ? cursorMapLookup(map, newState.selection.anchor) : null\n }\n\n return {\n pendingCm: pendingCmCursor,\n mappedTextOffset,\n }\n },\n },\n\n view(editorView) {\n const viewCount = awarenessRefCounts.get(awareness) ?? 0\n awarenessRefCounts.set(awareness, viewCount + 1)\n // Track field names for cleanup on last destroy\n let fieldNames = awarenessFieldNames.get(awareness)\n if (!fieldNames) {\n fieldNames = new Set()\n awarenessFieldNames.set(awareness, fieldNames)\n }\n fieldNames.add(cursorFieldName)\n if (sharedText) {\n fieldNames.add(cmCursorFieldName)\n }\n\n // Suppress awareness listener reactions when PM broadcasts to cmCursorFieldName\n let suppressCmReaction = false\n // Track last resolved CM cursor to avoid redundant pmCursor broadcasts\n let lastCmAbsAnchor = -1\n let lastCmAbsHead = -1\n // Set when the awareness listener already broadcast pmCursor for the\n // current CM cursor change, so pendingCm can skip its own broadcast.\n let cmCursorHandledByListener = false\n\n // When sharedText is available, yCollab writes the CM cursor (including\n // range selections) to awareness[cmCursorFieldName]. This listener\n // converts those Y.Text relative positions to PM positions and\n // broadcasts to pmCursor — so the app never needs to forward CM ranges.\n // Guard against reentrant calls: awareness.setLocalStateField emits\n // 'update' synchronously, which would re-enter handleAwarenessUpdate.\n // Without this guard, cursor resolution failure → setLocalStateField(null)\n // → 'update' event → handleAwarenessUpdate → resolution failure again\n // → setLocalStateField(null) → infinite recursion → stack overflow.\n let inAwarenessHandler = false\n const handleAwarenessUpdate = (\n { added, updated }: { added: number[]; updated: number[]; removed: number[] },\n ) => {\n if (inAwarenessHandler) return\n if (suppressCmReaction) return\n if (!sharedText?.doc) return\n const localId = awareness.clientID\n if (!updated.includes(localId) && !added.includes(localId)) return\n\n const localState = awareness.getLocalState() as Record<string, unknown> | null\n if (!localState) return\n\n inAwarenessHandler = true\n try {\n const cmCursor = localState[cmCursorFieldName] as\n | { anchor: RelativePosition; head: RelativePosition }\n | undefined\n if (!cmCursor?.anchor || !cmCursor?.head) {\n // CM cursor was cleared — also clear pmCursor if it was previously set\n if (lastCmAbsAnchor !== -1) {\n awareness.setLocalStateField(cursorFieldName, null)\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n }\n return\n }\n\n const absAnchor = createAbsolutePositionFromRelativePosition(cmCursor.anchor, sharedText.doc!)\n const absHead = createAbsolutePositionFromRelativePosition(cmCursor.head, sharedText.doc!)\n if (!absAnchor || !absHead) {\n // Relative position can't be resolved — clear stale PM cursor\n awareness.setLocalStateField(cursorFieldName, null)\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n return\n }\n // Reject payloads from different Y.Text instances (e.g., when\n // multiple views share one Awareness but use different Y.Text).\n if (absAnchor.type !== sharedText || absHead.type !== sharedText) {\n return\n }\n\n // Skip if unchanged (prevents loops from our own pmCursor broadcast)\n if (absAnchor.index === lastCmAbsAnchor && absHead.index === lastCmAbsHead) return\n\n const map = getOrBuildMap(editorView.state.doc)\n if (!map) {\n awareness.setLocalStateField(cursorFieldName, null)\n return\n }\n const pmAnchor = reverseCursorMapLookup(map, absAnchor.index)\n const pmHead = reverseCursorMapLookup(map, absHead.index)\n if (pmAnchor === null || pmHead === null) {\n // Mapping failed — clear stale PM cursor\n awareness.setLocalStateField(cursorFieldName, null)\n return\n }\n\n // Only update dedup cache after successful mapping — failed mapping\n // must not prevent retry when the doc changes and the map resolves.\n lastCmAbsAnchor = absAnchor.index\n lastCmAbsHead = absHead.index\n\n // Only suppress pendingCm broadcast when there IS a pending\n // CM cursor that would be redundant with the listener's broadcast.\n // Without this guard, the flag can stay true across awareness events\n // with no PM transaction, suppressing a later syncCmCursor broadcast.\n if (pendingCmCursor != null) {\n cmCursorHandledByListener = true\n }\n const ok = broadcastPmCursor(awareness, cursorFieldName, editorView, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n } catch {\n // Malformed cursor payload (e.g., non-RelativePosition from a\n // misbehaving peer) or resolution failure — clear stale cursor\n // to prevent ghost cursors, and reset dedup cache.\n awareness.setLocalStateField(cursorFieldName, null)\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n } finally {\n inAwarenessHandler = false\n }\n }\n\n if (sharedText) {\n awareness.on('update', handleAwarenessUpdate)\n }\n\n return {\n update(view, prevState) {\n // Invalidate dedup cache when doc changes so awareness listener\n // re-broadcasts pmCursor even if text offsets haven't changed.\n // Also re-trigger the handler to resolve cursors that failed mapping\n // when the doc was stale (no new awareness update would fire).\n if (view.state.doc !== prevState.doc) {\n lastCmAbsAnchor = -1\n lastCmAbsHead = -1\n if (sharedText?.doc && !suppressCmReaction) {\n handleAwarenessUpdate({ added: [awareness.clientID], updated: [], removed: [] })\n }\n }\n\n // CM → awareness: broadcast when pendingCmCursor is set.\n // Uses closure variable so appendTransaction cannot clear it.\n // If the awareness listener already converted yCollab's range to\n // pmCursor, skip here to avoid overwriting with collapsed data.\n if (pendingCmCursor != null) {\n const cursor = pendingCmCursor\n pendingCmCursor = null\n if (!cmCursorHandledByListener) {\n const map = getOrBuildMap(view.state.doc)\n const pmAnchor = map ? reverseCursorMapLookup(map, cursor.anchor) : null\n const pmHead = map ? reverseCursorMapLookup(map, cursor.head) : null\n if (pmAnchor !== null && pmHead !== null) {\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, pmAnchor, pmHead)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n // (When yCollab is active the listener handles this; this path\n // covers the case where syncCmCursor is called without yCollab.)\n if (sharedText?.doc) {\n broadcastTextCursor(\n awareness,\n cmCursorFieldName,\n sharedText,\n cursor.anchor,\n cursor.head,\n )\n }\n } else {\n // Mapping failed — clear stale cursors\n awareness.setLocalStateField(cursorFieldName, null)\n if (sharedText?.doc) {\n awareness.setLocalStateField(cmCursorFieldName, null)\n }\n }\n }\n cmCursorHandledByListener = false\n return\n }\n\n // PM → awareness: auto-broadcast on selection/doc change when focused.\n // suppressCmReaction wraps ALL awareness writes so the listener\n // does not echo stale CM cursor back to pmCursor.\n if (\n view.hasFocus() &&\n (view.state.selection !== prevState.selection ||\n view.state.doc !== prevState.doc)\n ) {\n suppressCmReaction = true\n try {\n const { anchor, head } = view.state.selection\n const ok = broadcastPmCursor(awareness, cursorFieldName, view, anchor, head)\n if (!ok && !warnedSyncPluginMissing) {\n warnedSyncPluginMissing = true\n warn({ code: 'ysync-plugin-missing', message: 'ySyncPlugin state not available — cursor broadcast skipped' })\n }\n // Also broadcast CM-format cursor so remote yCollab can render it.\n if (sharedText?.doc) {\n const map = getOrBuildMap(view.state.doc)\n const textAnchor = map ? cursorMapLookup(map, anchor) : null\n const textHead = map ? cursorMapLookup(map, head) : null\n if (textAnchor !== null && textHead !== null) {\n broadcastTextCursor(awareness, cmCursorFieldName, sharedText, textAnchor, textHead)\n } else {\n // Mapping failed — clear stale CM cursor\n awareness.setLocalStateField(cmCursorFieldName, null)\n }\n }\n } finally {\n suppressCmReaction = false\n }\n }\n\n // Always reset so a stale flag from a previous awareness update\n // cannot suppress a later syncCmCursor broadcast.\n cmCursorHandledByListener = false\n },\n destroy() {\n // Remove listener BEFORE clearing fields to avoid infinite\n // recursion: setLocalStateField emits 'update' synchronously,\n // which would re-enter handleAwarenessUpdate while the cursor\n // field is still set, causing broadcastPmCursor → update → …\n if (sharedText) {\n awareness.off('update', handleAwarenessUpdate)\n }\n // Only clear cursor fields when the last view using this\n // awareness is destroyed. Otherwise, destroying one view in\n // a multi-view setup clears cursors for all remaining views.\n const remaining = (awarenessRefCounts.get(awareness) ?? 1) - 1\n if (remaining <= 0) {\n awarenessRefCounts.delete(awareness)\n // Clear all cursor field names registered by any instance\n // using this Awareness, preventing ghost cursors from\n // instances that used different field names.\n const fields = awarenessFieldNames.get(awareness)\n if (fields) {\n for (const field of fields) {\n awareness.setLocalStateField(field, null)\n }\n awarenessFieldNames.delete(awareness)\n }\n } else {\n awarenessRefCounts.set(awareness, remaining)\n }\n },\n }\n },\n })\n}\n\n/**\n * Dispatch a CodeMirror cursor offset (or range) to the cursor sync plugin.\n * The plugin will convert it to a ProseMirror position and broadcast via awareness.\n *\n * @param view - The ProseMirror EditorView that has the cursor sync plugin installed.\n * @param anchor - CodeMirror text offset for the anchor.\n * @param head - CodeMirror text offset for the head (defaults to `anchor` for a collapsed cursor).\n * @param onWarning - Optional warning callback. Default `console.warn`.\n */\nexport function syncCmCursor(view: EditorView, anchor: number, head?: number, onWarning?: OnWarning): void {\n if (!cursorSyncPluginKey.getState(view.state)) {\n (onWarning ?? defaultOnWarning)({ code: 'cursor-sync-not-installed', message: 'cursor sync plugin is not installed on this EditorView' })\n return\n }\n const sanitize = (v: number) => { const n = Math.floor(v); return Number.isFinite(n) ? Math.max(0, n) : 0 }\n view.dispatch(\n view.state.tr.setMeta(cursorSyncPluginKey, {\n anchor: sanitize(anchor),\n head: sanitize(head ?? anchor),\n }),\n )\n}\n"],"mappings":"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","names":["transaction","doc","value","import_y_prosemirror","import_prosemirror_state","import_y_prosemirror","defaultOnWarning","import_core"]}