@panphora/clayjs 1.25.1 → 1.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/all.js +1 -1
  2. package/dist/clay-dom.js +1 -1
  3. package/dist/clay-events.js +1 -1
  4. package/dist/clay-internals.js +1 -1
  5. package/dist/clay-options.js +1 -1
  6. package/dist/clay-router.js +1 -1
  7. package/dist/clay-ui.js +1 -1
  8. package/dist/clay-utils.js +1 -1
  9. package/dist/clay.js +1 -1
  10. package/dist/clay.standalone.js +1141 -345
  11. package/dist/m/c-2EPHRF47.js +1 -0
  12. package/dist/m/{c-2GB34XL4.js → c-2SUFMUZX.js} +2 -2
  13. package/dist/m/{c-BHLYQGTA.js → c-3S3647J6.js} +1 -1
  14. package/dist/m/{c-QL4NYM2L.js → c-3S5TNRZA.js} +1 -1
  15. package/dist/m/{c-3Y234AUB.js → c-3XGLJAS7.js} +2 -2
  16. package/dist/m/{c-O4YIDULY.js → c-42WBOKFM.js} +1 -1
  17. package/dist/m/{c-42RLKGEY.js → c-4TP3UXBT.js} +1 -1
  18. package/dist/m/{c-ASGHHLFX.js → c-5ELELG2F.js} +1 -1
  19. package/dist/m/{c-T5IWJOMW.js → c-5ZYLTN3Q.js} +1 -1
  20. package/dist/m/{c-DDCYVLGG.js → c-67TBU56I.js} +1 -1
  21. package/dist/m/{c-WNOLMHI2.js → c-6BGJAJ67.js} +1 -1
  22. package/dist/m/{c-XS7G6U3Z.js → c-ABOBD7F3.js} +1 -1
  23. package/dist/m/{c-5IIEOPIV.js → c-ARO6CPMA.js} +1 -1
  24. package/dist/m/{c-XUZMRQ7O.js → c-BCS3AZQZ.js} +1 -1
  25. package/dist/m/c-BOS7GONO.js +1 -0
  26. package/dist/m/{c-NICICT4P.js → c-DEYHCYJB.js} +1 -1
  27. package/dist/m/{c-T7LOTEWO.js → c-DZT6W2NG.js} +1 -1
  28. package/dist/m/c-EHJWIGWN.js +1 -0
  29. package/dist/m/{c-V2V5FKXX.js → c-EIUKME3W.js} +1 -1
  30. package/dist/m/{c-ASMLERFA.js → c-EXZOA6XY.js} +1 -1
  31. package/dist/m/{c-STAPQ2X6.js → c-EYFMAR3M.js} +1 -1
  32. package/dist/m/{c-DVMDYRAE.js → c-FUN6AKFE.js} +1 -1
  33. package/dist/m/{c-XMIDHEKF.js → c-GY36NO7C.js} +1 -1
  34. package/dist/m/{c-TWB5ZQU4.js → c-H47C6PJZ.js} +1 -1
  35. package/dist/m/c-KD2ME5UN.js +2 -0
  36. package/dist/m/{c-GO635CRU.js → c-LAKBBJOU.js} +2 -2
  37. package/dist/m/{c-GNFDX6SU.js → c-LETPPG2F.js} +1 -1
  38. package/dist/m/{c-LS7IVQVW.js → c-MMV5JULA.js} +1 -1
  39. package/dist/m/{c-NEQRFC4O.js → c-MVVEIJUU.js} +1 -1
  40. package/dist/m/{c-XCOQNISP.js → c-N362HNGO.js} +1 -1
  41. package/dist/m/{c-7K4SCQB7.js → c-ND3W7D5U.js} +1 -1
  42. package/dist/m/{c-4NAYDOWH.js → c-NNHNXD6M.js} +1 -1
  43. package/dist/m/{c-DAUQ6JZ3.js → c-NYJ577NL.js} +1 -1
  44. package/dist/m/{c-3AAQJAKJ.js → c-PWILUN4T.js} +1 -1
  45. package/dist/m/{c-7Z2625LX.js → c-QA3Q3MDI.js} +1 -1
  46. package/dist/m/{c-2A2ATCIH.js → c-QKOKMQXA.js} +1 -1
  47. package/dist/m/{c-TGIMBAWE.js → c-RBWVF4N5.js} +1 -1
  48. package/dist/m/{c-A5IYL7JI.js → c-SPOEDIMP.js} +1 -1
  49. package/dist/m/{c-ZJ7726FO.js → c-SRW5OBCF.js} +1 -1
  50. package/dist/m/{c-A3OKSYRI.js → c-SYOA4T7V.js} +1 -1
  51. package/dist/m/{c-MHTO3QAD.js → c-TJ5PFOJ2.js} +1 -1
  52. package/dist/m/{c-NW4ZFKCY.js → c-TKV273YU.js} +1 -1
  53. package/dist/m/{c-D4N4UJCQ.js → c-UEKJWXCB.js} +2 -2
  54. package/dist/m/{c-44WITKWQ.js → c-V4DBV5ZV.js} +1 -1
  55. package/dist/m/c-WHMZCRQD.js +6 -0
  56. package/dist/m/{c-WMRJKQ2V.js → c-WHQUZM2V.js} +1 -1
  57. package/dist/m/{c-C27EVUQ6.js → c-XHD7XJTE.js} +1 -1
  58. package/dist/m/{c-PP3CUF3U.js → c-XONFK3WC.js} +1 -1
  59. package/dist/m/c-YYIN4NAF.js +30 -0
  60. package/dist/m/c-ZFAPQQIB.js +1 -0
  61. package/dist/m/core/{conflict-notice-Y2NTMDGJ.js → conflict-notice-UNDOD4BL.js} +1 -1
  62. package/dist/m/core/site-edits-C22O53GS.js +1 -0
  63. package/dist/m/loader-DCKCZKY6.js +1 -0
  64. package/dist/m/{manifest-A27A4278.js → manifest-7954CBE3.js} +1 -1
  65. package/dist/m/{manifest-edit-E8A61EB4.js → manifest-edit-93AA79D7.js} +1 -1
  66. package/dist/m/manifest.json +442 -441
  67. package/dist/m/page/{editor-HS2CAV3W.js → editor-UF3CIA6V.js} +2 -2
  68. package/dist/m/plugins/{cms-4KET6RZT.js → cms-KG6TCTR3.js} +1 -1
  69. package/dist/m/plugins/{live-CO4VJS46.js → live-DF5YS5AH.js} +1 -1
  70. package/dist/m/plugins/page-GZSU77JS.js +2 -0
  71. package/dist/m/plugins/{page-view-6KNTXABH.js → page-view-7MM3FEAR.js} +1 -1
  72. package/dist/m/plugins/{quickcrop-VNRXGRPC.js → quickcrop-UHURKA2F.js} +2 -2
  73. package/dist/m/plugins/richclay-MXH2GFAZ.js +549 -0
  74. package/dist/m/plugins/undo-KLHAGV75.js +1 -0
  75. package/dist/m/sync/{live-sync-DX4K5GDB.js → live-sync-RZQSDMZW.js} +1 -1
  76. package/dist/offline-manifest.json +360 -360
  77. package/package.json +1 -1
  78. package/src/core/site-edits.js +1 -1
  79. package/src/loader-logic.js +0 -13
  80. package/src/page/boot-pack.js +1 -0
  81. package/src/page/history-intent.js +111 -4
  82. package/src/page/ybind/bridge.js +14 -7
  83. package/src/page/ybind/moves.js +437 -3
  84. package/src/page/ybind/origins.js +1 -1
  85. package/src/page/ybind/recovery.js +1 -1
  86. package/src/plugins/page.js +48 -4
  87. package/src/plugins/richclay.js +136 -0
  88. package/src/plugins/undo.js +169 -7
  89. package/src/recovery/undo.js +14 -7
  90. package/src/sync/live-sync.js +24 -0
  91. package/src/ui/vendor-skin.js +9 -4
  92. package/dist/m/c-3XNTQQEJ.js +0 -1
  93. package/dist/m/c-4OZZ7ENT.js +0 -30
  94. package/dist/m/c-D2OPWOWO.js +0 -2
  95. package/dist/m/c-LUBZU4AZ.js +0 -6
  96. package/dist/m/c-LYNLY3MR.js +0 -1
  97. package/dist/m/c-MLMJUNRV.js +0 -1
  98. package/dist/m/c-V5L6HWVL.js +0 -1
  99. package/dist/m/core/site-edits-TIJDPG6H.js +0 -1
  100. package/dist/m/loader-7ORQVRCK.js +0 -1
  101. package/dist/m/plugins/page-L3VKIQU2.js +0 -2
  102. package/dist/m/plugins/richclay-V6WKJTUF.js +0 -549
  103. package/dist/m/plugins/undo-WUK2NLBA.js +0 -1
  104. /package/dist/m/plugins/{quickcrop-VNRXGRPC.js.LEGAL.txt → quickcrop-UHURKA2F.js.LEGAL.txt} +0 -0
  105. /package/dist/m/plugins/{richclay-V6WKJTUF.js.LEGAL.txt → richclay-MXH2GFAZ.js.LEGAL.txt} +0 -0
@@ -5,9 +5,145 @@ import { installSkin } from "../ui/vendor-skin.js";
5
5
  import { CSS, ROOTS } from "../ui/skins/richclay.js";
6
6
  import { RichClay } from "../vendor/richclay.vendor.js";
7
7
  import { startsAtImport } from "../core/is-edit-mode.js";
8
+ import { stripEphemeral } from "../lib/ephemeral.js";
9
+ import { loadedEngine, mergeEngine } from "../sync/merge-engine.js";
8
10
 
9
11
  installSkin("richclay", CSS, { roots: ROOTS });
10
12
 
13
+ // A field the author kept out of page undo: its own literal no-undo (not the one RichClay
14
+ // adds while it runs), a clay="no-undo" token on it, or an ancestor region with no-undo.
15
+ const authoredNoUndo = (node) => {
16
+ if (node.hasAttribute("no-undo") && !node.hasAttribute("data-richclay-runtime-no-undo")) return true;
17
+ if ((node.getAttribute("clay") || "").split(/\s+/).includes("no-undo")) return true;
18
+ const parent = node.parentElement;
19
+ return !!parent && window.clay?.region?.resolveRegionPolicy?.(parent)?.undoable === false;
20
+ };
21
+ // The editor tidies a field's source whitespace the first time it is typed in. Whitespace
22
+ // that sits beside a block, or at the start or end of one, shows nothing, so it never
23
+ // counts as a change: a person's first letter is one step, and the tidy is not a step of
24
+ // its own or part of another field's.
25
+ const BLOCK = /^(P|DIV|UL|OL|LI|H[1-6]|BLOCKQUOTE|TABLE|THEAD|TBODY|TR|TD|TH|FIGURE|FIGCAPTION|HR|SECTION|ARTICLE|ASIDE|HEADER|FOOTER|DL|DT|DD)$/;
26
+ const isBlock = (node) => node?.nodeType === 1 && BLOCK.test(node.nodeName);
27
+ const dropLayoutWhitespace = (root) => {
28
+ const walker = root.ownerDocument.createTreeWalker(root, 4);
29
+ const blank = [];
30
+ for (let text = walker.nextNode(); text; text = walker.nextNode()) {
31
+ if (/\S/.test(text.data) || text.parentElement?.closest("pre")) continue;
32
+ const parent = text.parentNode;
33
+ const edge = !text.previousSibling || !text.nextSibling;
34
+ if ((edge && (parent === root || isBlock(parent))) || isBlock(text.previousSibling) || isBlock(text.nextSibling)) blank.push(text);
35
+ }
36
+ blank.forEach((text) => text.remove());
37
+ };
38
+ // Ephemeral attributes (selection marks and the like) never save, so they come off a
39
+ // copy before the comparison, as they do for every other node undo certifies.
40
+ const savedForm = (node) => {
41
+ const copy = node.cloneNode(true);
42
+ stripEphemeral(copy);
43
+ dropLayoutWhitespace(copy);
44
+ return RichClay.savedContent(copy);
45
+ };
46
+
47
+ // Parts of a field that history does not own (editor chrome, a widget the author marked
48
+ // no-undo) never count as a change: the sample holds a placeholder for each, and a replay
49
+ // puts back what each one holds now.
50
+ const UNOWNED = '[editor-ui], [clay~="editor-ui"], [no-undo]:not([data-richclay-runtime-no-undo]), [clay~="no-undo"]';
51
+ const outermost = (root) => [...root.querySelectorAll(UNOWNED)].filter((el) => {
52
+ for (let at = el.parentElement; at && at !== root; at = at.parentElement) if (at.matches(UNOWNED)) return false;
53
+ return true;
54
+ });
55
+ const historyForm = (node) => {
56
+ const copy = node.cloneNode(true);
57
+ outermost(copy).forEach((el) => el.replaceWith(copy.ownerDocument.createElement("clay-kept")));
58
+ return savedForm(copy);
59
+ };
60
+ const withUnowned = (element, html) => {
61
+ if (!html.includes("<clay-kept")) return html;
62
+ const template = element.ownerDocument.createElement("template");
63
+ template.innerHTML = html;
64
+ const live = outermost(element);
65
+ [...template.content.querySelectorAll("clay-kept")].forEach((slot, at) => {
66
+ if (live[at]) slot.replaceWith(live[at].cloneNode(true));
67
+ else slot.remove();
68
+ });
69
+ return template.innerHTML;
70
+ };
71
+
72
+ // Undo certifies a rich field by the content it would save, so editor runtime state
73
+ // never refuses a step and a real text change always does. A field the author marked
74
+ // no-undo keeps its protection.
75
+ const richForm = (node) => (!node.matches?.(RichClay.selector) || authoredNoUndo(node) ? undefined : savedForm(node));
76
+
77
+ // The field's own typing is a step of the page history: the engine reads the content the
78
+ // field would save and, on a replay, hands it back through the host write. The editor's own
79
+ // history is off while the page owns it, so a field's Cmd+Z and its toolbar reach the same
80
+ // stack as everything else.
81
+ // The engine rebaselines the field right after the write, so the write must land before
82
+ // it returns. A loaded merge engine morphs at once. The first undo asks for it, so a boot
83
+ // pays nothing; until it lands a plain innerHTML write is exact for saved content (checked
84
+ // in Chrome), except in a field holding editor chrome, which only the morph keeps.
85
+ const writeInner = (root, html) => { root.innerHTML = html; };
86
+ const morphNow = (field) => {
87
+ const engine = loadedEngine();
88
+ if (engine) return engine.morph;
89
+ mergeEngine().catch(() => {});
90
+ return field.querySelector('[editor-ui], [clay~="editor-ui"]') ? undefined : writeInner;
91
+ };
92
+
93
+ // Rich fields belong to the page history only when the page records typing: in commands
94
+ // mode the page's history holds commands alone, and a field's own history is left to the
95
+ // editor, which is then the only history that has the typing.
96
+ const pageOwnsTyping = () => window.clay?.undo?.pageHistory === true && window.clay.undo.recordingMode !== "commands";
97
+
98
+ const tracked = new Map();
99
+
100
+ const track = (field) => {
101
+ const undo = window.clay?.undo;
102
+ if (!pageOwnsTyping() || authoredNoUndo(field)) return;
103
+ tracked.set(field, undo.trackContent(field, { read: historyForm, write: (element, html) => { RichClay.setSavedContent(element, withUnowned(element, html), { morph: morphNow(element) }); } }));
104
+ if (!loadedEngine() && field.querySelector('[editor-ui], [clay~="editor-ui"]') && typeof undo.hold === "function") {
105
+ const release = undo.hold();
106
+ mergeEngine().catch(() => {}).finally(release);
107
+ }
108
+ };
109
+
110
+ const untrackAll = () => {
111
+ for (const dispose of tracked.values()) dispose();
112
+ tracked.clear();
113
+ };
114
+
115
+ document.addEventListener("richclay:mount", (event) => track(event.target));
116
+
117
+ // The rich form is handed to the page's undo when its session starts, rather than through
118
+ // a shared module, so a page without undo loads nothing for it. Each undo engine is asked
119
+ // once.
120
+ let formedFor = null;
121
+ const followHistory = () => {
122
+ const undo = window.clay?.undo;
123
+ if (undo?.addDurableForm && formedFor !== undo) {
124
+ undo.addDurableForm(richForm);
125
+ formedFor = undo;
126
+ }
127
+ RichClay.hostHistory = pageOwnsTyping();
128
+ RichClay.keepsOwnHistory = authoredNoUndo;
129
+ if (!RichClay.hostHistory) untrackAll();
130
+ else document.querySelectorAll("[data-richclay-active]").forEach(track);
131
+ };
132
+
133
+ document.addEventListener("clay:undo-session", followHistory);
134
+ document.addEventListener("clay:undo-recording", followHistory);
135
+ // A boot starts the undo session while its own module is still being imported, so the
136
+ // session event can land before the loader has put `clay.undo` on the page: once every
137
+ // plugin is attached the same answer is asked for again.
138
+ document.addEventListener("clay:ready", followHistory);
139
+
140
+ document.addEventListener("richclay:history-exhausted", (event) => {
141
+ const undo = window.clay?.undo;
142
+ if (!undo || window.clay.modeLifecycle?.canWrite?.() === false) return;
143
+ if (event.detail?.direction === "redo") undo.redo();
144
+ else undo.undo();
145
+ });
146
+
11
147
  let session = null;
12
148
 
13
149
  const fault = (message, code) => Object.assign(new Error(`clay:richclay: ${message}`), { code });
@@ -11,6 +11,56 @@ import { PERSIST } from '../lib/attr-aliases.js'
11
11
  import { isGestureItem, isRuntimeAttribute } from '../lib/undo/capture-move.js'
12
12
  import { gateMarkDirty } from '../lib/dirty-gate.js'
13
13
 
14
+ // Where the caret goes after an undo or redo: at the text that changed.
15
+ //
16
+ // The engine's receipt carries a hint for it: `{ node, offset }` for a text run, and
17
+ // `{ root, textOffset }` for a field whose saved content is its text. The person goes on
18
+ // typing from there, so the field takes focus when they were already typing in a field.
19
+ // When they were not (a button, the dock's Undo on a phone), the field is only scrolled
20
+ // into view: focusing it would open the keyboard. A structural step carries no hint, so
21
+ // an item coming back moves neither the caret nor the focus.
22
+
23
+ const EDITABLE = '[contenteditable]:not([contenteditable="false"])'
24
+
25
+ // The text node an offset into the field's text lands on: count the field's text nodes
26
+ // until the offset is inside one. Past the end is the end of the last text node, and an
27
+ // empty field is the field itself.
28
+ function resolve(root, textOffset) {
29
+ let left = Number(textOffset) || 0
30
+ if (left < 0) left = 0
31
+ const doc = root.ownerDocument || document
32
+ const walker = doc.createTreeWalker(root, 4)
33
+ let last = null
34
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
35
+ if (left <= node.data.length) return { node, offset: left }
36
+ left -= node.data.length
37
+ last = node
38
+ }
39
+ return last ? { node: last, offset: last.data.length } : { node: root, offset: root.childNodes.length }
40
+ }
41
+
42
+ // The field the hint is inside. A field in a hidden section is neither scrolled to nor
43
+ // focused, and a node that is no longer in the page has nowhere to put a caret.
44
+ function hostOf(node) {
45
+ const element = node.nodeType === 1 ? node : node.parentElement
46
+ const host = element?.closest?.(EDITABLE)
47
+ if (!host || !host.isConnected) return null
48
+ if (typeof host.checkVisibility === 'function' && host.checkVisibility() === false) return null
49
+ return host
50
+ }
51
+
52
+ function placeCaret(hint) {
53
+ if (!hint) return
54
+ const at = hint.root ? resolve(hint.root, hint.textOffset) : hint
55
+ if (!at?.node) return
56
+ const host = hostOf(at.node)
57
+ if (!host) return
58
+ if (typeof host.scrollIntoView === 'function') host.scrollIntoView({ block: 'nearest', inline: 'nearest' })
59
+ if (!document.activeElement?.closest?.(EDITABLE)) return
60
+ if (document.activeElement !== host) host.focus({ preventScroll: true })
61
+ document.getSelection()?.collapse(at.node, at.offset)
62
+ }
63
+
14
64
  // Tab-local attributes (ephemeral state, route-active, root library state) are
15
65
  // never undone, and neither is what a drag's vendor writes on the item it holds:
16
66
  // `draggable` and `style` are the gesture's own bookkeeping, and a click that
@@ -20,6 +70,17 @@ import { gateMarkDirty } from '../lib/dirty-gate.js'
20
70
  // The page's own maintenance attributes, kept outside history by name (a card number a
21
71
  // repair rewrites, for example): saved like any attribute, never an edit to undo.
22
72
  const pageIgnored = new Set()
73
+ // What a structural undo step compares for a node whose live markup is not its saved
74
+ // markup. An editor that owns a field registers one function (`clay.undo.addDurableForm`):
75
+ // it answers a string for a node it owns and undefined for every other node.
76
+ const durableForms = new Set()
77
+ const durableFormOf = (node, host = null) => {
78
+ for (const form of durableForms) {
79
+ const own = form(node, host)
80
+ if (typeof own === 'string') return own
81
+ }
82
+ return undefined
83
+ }
23
84
  const withTabLocal = (opts = {}) => {
24
85
  const own = opts.ignoreAttribute
25
86
  return {
@@ -27,15 +88,18 @@ const withTabLocal = (opts = {}) => {
27
88
  ignoreAttribute: (name, target, host) => pageIgnored.has(name) || isTabLocalAttr(name, target) || isDerivedAttr(target, name, host) || isRuntimeAttribute(name, target) || (own ? own(name, target, host) : false),
28
89
  isProtected: opts.isProtected || keepsOwnHistory,
29
90
  ignoreNode: opts.ignoreNode || isGestureItem,
91
+ durableForm: opts.durableForm || durableFormOf,
30
92
  }
31
93
  }
32
94
 
33
- // Saved content that undo does not record (a richclay region, a no-undo block) has a
34
- // history undo cannot restore, so a parent's undo or redo must not erase it. Chrome
35
- // that is never saved (editor-ui) loses nothing and is not protected.
95
+ // Saved content that undo does not record (an authored no-undo block) has a history
96
+ // undo cannot restore, so a parent's undo or redo must not erase it. A node with a
97
+ // durable form (a mounted rich field) is certified by that form instead, and chrome
98
+ // that is never saved (editor-ui) loses nothing; neither is protected.
36
99
  function keepsOwnHistory(node) {
37
100
  const policy = window.clay?.region?.resolveRegionPolicy
38
101
  if (!policy || node.nodeType !== 1) return false
102
+ if (durableFormOf(node) !== undefined) return false
39
103
  const own = policy(node)
40
104
  if (own.undoable !== false || own.persist === 'none') return false
41
105
  const parent = node.parentElement
@@ -52,9 +116,12 @@ let recording = null
52
116
  const setRecording = undo.setRecording.bind(undo)
53
117
  undo.setRecording = (mode) => {
54
118
  recording = mode ?? null
119
+ document.dispatchEvent(new CustomEvent('clay:undo-recording', { detail: { mode: recording } }))
55
120
  return setRecording(mode)
56
121
  }
57
122
 
123
+ Object.defineProperty(undo, 'recordingMode', { get: () => recording, configurable: true })
124
+
58
125
  // An authored history action is a user edit, and it writes the DOM while a sync
59
126
  // frame's apply may hold both the mutation hub and the gate paused: neither feed
60
127
  // records it then, and the apply would take the write into its saved baseline.
@@ -73,6 +140,17 @@ undo.command = (label, fn) => {
73
140
  // Undo and redo mark it in afterReplay, which the engine calls on every path that
74
141
  // replays a step: the facade and the keyboard shortcut bound to the engine itself.
75
142
 
143
+ // One entry holding one step that another editor stands behind (the block editor's own
144
+ // Yjs stack item). It goes through the engine's own command, whose flush closes whatever
145
+ // the page was typing, so the order of the two histories is the order of the edits. The
146
+ // editor's step is not a document write of its own, so recording it never marks the page
147
+ // dirty; the editor's own dirty feed is the one that does.
148
+ undo.recordStep = (label, primitive) => command(label, () => undo.record(primitive))
149
+
150
+ // A step that stands for an editor's own undo item holds tokens, not text: its prop names
151
+ // it, and what a person wrote lives in the editor, not in a recovery copy.
152
+ const isBlockStep = (entry) => !!entry?.steps?.length && entry.steps.every((step) => step.kind === 'value' && /^blockeditor-step(?::|$)/.test(step.prop))
153
+
76
154
  // A commit inside clay.effect drains the paused hub's records, so the effect's
77
155
  // own writes reach autosave and re-trigger it. Warn once; the commit still runs.
78
156
  const commit = undo.commit.bind(undo)
@@ -131,8 +209,9 @@ function restoreControls() {
131
209
  // undo or redo never reaches the control restore.
132
210
  const afterReplay = (receipt) => {
133
211
  if (!receipt?.ok) return
134
- gateMarkDirty()
212
+ if (!isBlockStep(undo.describe(receipt.entryId))) gateMarkDirty()
135
213
  restoreControls()
214
+ if (receipt.caret && undo.pageHistory) placeCaret(receipt.caret)
136
215
  }
137
216
 
138
217
  // A refusal is announced for whoever presents it: the engine's receipt, once.
@@ -143,6 +222,17 @@ const refuse = ({ entryId, label, direction, code }) => {
143
222
  keepRefused({ ok: false, code, entryId, label, direction })
144
223
  }
145
224
 
225
+ // A step the engine can no longer replay is skipped instead of blocking the press: the
226
+ // person asked once and gets the next older step. Its recovery copy is made from the
227
+ // description the engine hands over, because the entry is already out of the history.
228
+ const skip = ({ entryId, label, direction, code, description }) => {
229
+ document.dispatchEvent(new CustomEvent('clay:undo-skipped', {
230
+ detail: { entryId, label, direction, code },
231
+ }))
232
+ if (isBlockStep(description)) return
233
+ keepRefused({ ok: false, code, entryId, label, direction, description })
234
+ }
235
+
146
236
  // A refused step is kept as recovery before a reload can take it away: the refusal is
147
237
  // announced synchronously above and the copy is made behind it, so a store that will
148
238
  // not answer can never hold up the receipt. The module is fetched on the first refusal
@@ -185,6 +275,11 @@ const withRecovery = (call, fallback) => (...args) =>
185
275
  // An app's kept copy is announced like a refused step's, so the review notice shows it
186
276
  // in this session instead of only after a reload.
187
277
  const keepApp = withRecovery('keep', { ok: false, code: 'unavailable' })
278
+ undo.addDurableForm = (form) => {
279
+ durableForms.add(form)
280
+ return () => { durableForms.delete(form) }
281
+ }
282
+
188
283
  undo.ignoreAttribute = (name) => {
189
284
  const key = String(name || '')
190
285
  if (!key) return () => {}
@@ -235,9 +330,48 @@ undo.reviewActionsFor = (record) => {
235
330
  // after every entry, replay, refusal, dismissal and clear, and when a session starts or
236
331
  // ends. `undo` and `redo` are the entries the next press would replay, with `refused`
237
332
  // set on the one that was just refused.
238
- const HISTORY_EVENTS = ['commit', 'undo', 'redo', 'refused', 'discard', 'clear']
333
+ const HISTORY_EVENTS = ['commit', 'undo', 'redo', 'refused', 'skipped', 'discard', 'clear']
334
+
335
+ // Whatever else owns the moment (a proposal preview, a dialog) holds the page history: while
336
+ // any hold is live the engine refuses a replay, so nothing behind the hold can be undone by a
337
+ // stray Cmd+Z, and the event reports neither direction as available.
338
+ const holds = new Set()
339
+
340
+ // The engine's own answer for each direction: a getter on the engine object itself, so it is
341
+ // read once here and called through, whatever else happens to the property.
342
+ const engineDirection = (name) => {
343
+ for (let owner = undo; owner; owner = Object.getPrototypeOf(owner)) {
344
+ const desc = Object.getOwnPropertyDescriptor(owner, name)
345
+ if (!desc) continue
346
+ if (typeof desc.get === 'function') return () => desc.get.call(undo)
347
+ return () => Boolean(desc.value.call(undo))
348
+ }
349
+ return () => false
350
+ }
351
+
352
+ const engineCanUndo = engineDirection('canUndo')
353
+ const engineCanRedo = engineDirection('canRedo')
354
+
355
+ // While a hold is live neither direction is available, so a dock button, the editor's own
356
+ // history and the event all answer the same thing.
357
+ Object.defineProperty(undo, 'canUndo', { get: () => holds.size === 0 && engineCanUndo(), configurable: true })
358
+ Object.defineProperty(undo, 'canRedo', { get: () => holds.size === 0 && engineCanRedo(), configurable: true })
359
+
360
+ undo.hold = () => {
361
+ const token = Symbol('hold')
362
+ holds.add(token)
363
+ announceChange()
364
+ let released = false
365
+ return () => {
366
+ if (released) return
367
+ released = true
368
+ holds.delete(token)
369
+ announceChange()
370
+ }
371
+ }
372
+
239
373
  const announceChange = () => {
240
- const live = !!session?.started
374
+ const live = !!session?.started && holds.size === 0
241
375
  document.dispatchEvent(new CustomEvent('clay:undo-change', {
242
376
  detail: {
243
377
  canUndo: live && undo.canUndo,
@@ -254,6 +388,22 @@ const announceChange = () => {
254
388
  // recovery service and outlive every session; nothing here reads, clears or rewrites them.
255
389
  let session = null
256
390
 
391
+ // There is one history in Edit and it is the page's: a live session is what puts a rich
392
+ // field's typing on it and gives Cmd+Z in any editable field to it. No attribute decides
393
+ // this, and a page that never loads this plugin never starts a session, so nothing about
394
+ // it changes.
395
+ Object.defineProperty(undo, 'pageHistory', {
396
+ get: () => !!session?.started,
397
+ configurable: true,
398
+ })
399
+
400
+ const announceSession = () => {
401
+ if (typeof document === 'undefined') return
402
+ document.dispatchEvent(new CustomEvent('clay:undo-session', {
403
+ detail: { started: !!session?.started, pageHistory: undo.pageHistory },
404
+ }))
405
+ }
406
+
257
407
  function ownership() {
258
408
  return { started: false, admitted: false, subscriptions: [] }
259
409
  }
@@ -283,18 +433,29 @@ function undoLease(record) {
283
433
  if (record.started) return
284
434
  record.started = true
285
435
  record.admitted = true
286
- undo.start(withTabLocal({ bindKeys: true, ...(recording ? { recording } : {}) }))
436
+ undo.start(withTabLocal({
437
+ bindKeys: true,
438
+ editableKeys: 'page',
439
+ ownKeysIn: ['.blockeditor-editor'],
440
+ typingBreak: 'sentence',
441
+ refused: 'skip',
442
+ holdReplay: () => holds.size > 0,
443
+ ...(recording ? { recording } : {}),
444
+ }))
287
445
  for (const type of ['undo', 'redo']) {
288
446
  undo.on(type, afterReplay)
289
447
  record.subscriptions.push({ type, handler: afterReplay })
290
448
  }
291
449
  undo.on('refused', refuse)
292
450
  record.subscriptions.push({ type: 'refused', handler: refuse })
451
+ undo.on('skipped', skip)
452
+ record.subscriptions.push({ type: 'skipped', handler: skip })
293
453
  for (const type of HISTORY_EVENTS) {
294
454
  undo.on(type, announceChange)
295
455
  record.subscriptions.push({ type, handler: announceChange })
296
456
  }
297
457
  announceChange()
458
+ announceSession()
298
459
  },
299
460
  quiesce() {
300
461
  if (!record.admitted) return
@@ -314,6 +475,7 @@ function undoLease(record) {
314
475
  for (const { type, handler } of record.subscriptions.splice(0)) undo.off(type, handler)
315
476
  undo.stop()
316
477
  announceChange()
478
+ announceSession()
317
479
  },
318
480
  }
319
481
  }
@@ -148,7 +148,9 @@ let keeping = Promise.resolve();
148
148
  * keeps it — and answers with the failure.
149
149
  *
150
150
  * @param {Object} engine - the started undo scope (`clay.undo`)
151
- * @param {{entryId: string, direction: string, code?: string}} receipt
151
+ * @param {{entryId: string, direction: string, code?: string, description?: ?Object}} receipt
152
+ * `description` is the engine's own `describe(entryId)` answer where the caller already has
153
+ * it, as a skipped step does: that entry is out of the history by the time it is kept.
152
154
  * @returns {Promise<{ok: boolean, code?: string}>}
153
155
  */
154
156
  export function keepRefusal(engine, receipt) {
@@ -160,11 +162,13 @@ export function keepRefusal(engine, receipt) {
160
162
  async function keepRefusalNow(engine, receipt) {
161
163
  const ch = channel();
162
164
  if (!ch) return { ok: false, code: 'unavailable' };
163
- let described = null;
164
- try {
165
- described = engine?.describe?.(receipt?.entryId) ?? null;
166
- } catch (_) {
167
- described = null;
165
+ let described = receipt?.description ?? null;
166
+ if (!described) {
167
+ try {
168
+ described = engine?.describe?.(receipt?.entryId) ?? null;
169
+ } catch (_) {
170
+ described = null;
171
+ }
168
172
  }
169
173
  if (!described) return { ok: false, code: 'unknown-entry' };
170
174
  const refused = described.refused && typeof described.refused === 'object' ? described.refused : {};
@@ -186,7 +190,10 @@ async function keepRefusalNow(engine, receipt) {
186
190
  try {
187
191
  staged = ch.stage({ key, kind: UNDO_KIND, baseEtag: representedEtag() ?? null, base: null, payload });
188
192
  } catch (err) {
189
- console.error('clayjs: the refused step could not be kept for a later reload; it is still in this tab\'s history', err);
193
+ const message = receipt?.description
194
+ ? 'clayjs: the skipped step could not be kept for a later reload; its text is lost when this tab does'
195
+ : 'clayjs: the refused step could not be kept for a later reload; it is still in this tab\'s history'
196
+ console.error(message, err)
190
197
  return { ok: false, code: 'error' };
191
198
  }
192
199
  const written = await ch.flush();
@@ -199,6 +199,27 @@ const REMOTE_WINS_SELECTOR = [
199
199
  '[clay~="no-watch"]', '[no-watch]', '[mutations-ignore]',
200
200
  ].join(',');
201
201
 
202
+ // Fields whose editor keeps its own undo history: every attribute RichClay mounts
203
+ // on (the vendor's RichClay.selector). That history is copies of the field's markup,
204
+ // and the editor's observer takes any change to the field for the person's typing,
205
+ // so a frame that rewrites a field has to say so before that observer runs, or the
206
+ // next undo there puts the older copy back over the collaborator's text.
207
+ const EDITOR_FIELD_SELECTOR = '[data-richclay], [richclay], [editable], [clay-editable]';
208
+
209
+ // Called right before a frame applies; what it returns is called right after, in
210
+ // the same tick, so no observer callback and no keystroke lands in between. Each
211
+ // field the frame changed is told with `clay:external-write`, the event a refreshed
212
+ // shared value sends (core/site-edits.js), and starts its history from the merged
213
+ // content. A field the frame left alone keeps its history.
214
+ function editorFieldsBeforeApply() {
215
+ const fields = Array.from(document.querySelectorAll(EDITOR_FIELD_SELECTOR), (field) => [field, field.innerHTML]);
216
+ return () => {
217
+ for (const [field, before] of fields) {
218
+ if (field.isConnected && field.innerHTML !== before) field.dispatchEvent(new CustomEvent('clay:external-write'));
219
+ }
220
+ };
221
+ }
222
+
202
223
  // Host tokens, library state, router state and ephemeral state, on every side
203
224
  // of a merge: the live root, a snapshot clone's root, a parsed frame's root.
204
225
  // Extension marker attributes too: snapshots and frames never carry them, so a
@@ -2011,6 +2032,7 @@ class LiveSync {
2011
2032
  let lineageCapture;
2012
2033
  const mergeDocument = (options) => {
2013
2034
  lineageCapture = beginLineageCapture(conflicts.list());
2035
+ const tellRewrittenFields = editorFieldsBeforeApply();
2014
2036
  try {
2015
2037
  const pending = loadedEngine().HyperMorph.mergeDocument({ ...options, lineage: lineageCapture.lineage });
2016
2038
  lineageCapture.returned();
@@ -2019,6 +2041,8 @@ class LiveSync {
2019
2041
  lineageCapture.invalidate(conflicts.list());
2020
2042
  lineageCapture.finish(conflicts.list());
2021
2043
  throw error;
2044
+ } finally {
2045
+ tellRewrittenFields();
2022
2046
  }
2023
2047
  };
2024
2048
  const store = this.identity;
@@ -26,9 +26,14 @@ const installed = new Map();
26
26
  // A vendor that sets no color-scheme of its own inherits the page's, and a page that
27
27
  // declares none computes `normal`, which resolves every light-dark() pin to light.
28
28
  // ClayJS's own surfaces follow the reader's preference there (pageScheme), so a skin
29
- // installed with its roots gives them the same scheme.
30
- function schemeRule(roots) {
31
- return `\n@layer clay-skin{:is(${roots.join(', ')}){color-scheme:${pageScheme()} !important}}`;
29
+ // installed with its roots gives them the same scheme. The roots sit in the page's DOM,
30
+ // so the page's ::selection would paint its text colour over a field the skin coloured;
31
+ // the roots get a fixed highlight pair that follows their scheme instead (system
32
+ // Highlight/HighlightText can pair dark text with a dark highlight on a dark Mac).
33
+ function rootRules(roots) {
34
+ const scope = `:is(${roots.join(', ')})`;
35
+ return `\n@layer clay-skin{${scope}{color-scheme:${pageScheme()} !important}` +
36
+ `${scope}::selection,${scope} ::selection{background-color:light-dark(#B4D5FE, #2F5A8C) !important;color:light-dark(#1A1C24, #F2F4F8) !important}}`;
32
37
  }
33
38
 
34
39
  /**
@@ -50,7 +55,7 @@ export function installSkin(name, css, { roots } = {}) {
50
55
  el.setAttribute('clay', RUNTIME_ONLY);
51
56
  el.setAttribute('data-clay-skin', name);
52
57
  }
53
- const text = roots ? css + schemeRule(roots) : css;
58
+ const text = roots ? css + rootRules(roots) : css;
54
59
  if (el.textContent !== text) el.textContent = text;
55
60
  if (!placed(head, el)) head.insertBefore(el, firstNonSkin(head));
56
61
  return el;
@@ -1 +0,0 @@
1
- import{a as ce,b as Qe,c as ue,e as fe,f as pe,g as nt}from"./c-5IIEOPIV.js";import{a as Ze,b as et,c as de,d as tt}from"./c-LZJPNR2W.js";import{a as A,b as J}from"./c-FILLTJA7.js";import{b as Ke}from"./c-5VYOWDTI.js";import{b as oe,c as ae,d as x,f as he,g as Ve,h as Xe}from"./c-P73XPAOA.js";import{a as q}from"./c-MZCOGNZL.js";import{a as Ge}from"./c-LD6UVV5R.js";import{a as ze,f as We,g as Ye,h as Je}from"./c-PGSBXHUE.js";import{a as se,b as $e,c as P,d as re,e as le,h as je}from"./c-KU3PX7LL.js";import{e as Ue}from"./c-EQ5NBOGA.js";import{d as Pe}from"./c-IJUK2GPW.js";import{f as $,j as te}from"./c-C7SUMNMI.js";import{a as Le}from"./c-BPF2NSPV.js";import{E as Y,F as Ne,H as T,I as Oe,b as D,j as ie,o as W,t as M,z as Be}from"./c-UW6MMZ4V.js";import{a as Re,b as qe,c as Me,e as j,g as z,h as ne}from"./c-MOP5KBWZ.js";import{m as Ie,n as Te}from"./c-HA6INT6J.js";import{a as He,b as Fe,l as Ce,m as Ae}from"./c-ICMAOOGK.js";function it(s){let e=new WeakMap,t=0,i=r=>e.get(r)||null,n=r=>{let l=e.get(r);return l||(l=`${s}:${++t}`,e.set(r,l)),l};return{idOf:i,ensure:n,adopt:(r,l)=>{r&&typeof l=="string"&&l&&e.set(r,l)},exportMap:(r,l)=>{let o={},a=[],h=[],c=(d,_)=>{let g=l(d);g&&(o[_]=n(g));let E=d.children;a.push(E.length),h.push(d.tagName.toLowerCase());for(let f=0;f<E.length;f++)c(E[f],_===""?String(f):`${_}.${f}`)};return c(r,""),o["~"]=a.join(","),o["^"]=h.join(","),o}}}function st(s,e){let t=new WeakMap;if(!s||!e||typeof e!="object"||Array.isArray(e))return t;let i=e["~"],n=typeof i=="string"?i.split(",").map(Number):null,r=n&&typeof e["^"]=="string"?e["^"].split(","):null,l=d=>Number.isInteger(d)&&d>=0,o=0,a=!1,h=d=>{let _=d;for(;_>0;){let g=n[o++];if(o>n.length||!l(g))return!1;_+=g-1}return!0},c=(d,_)=>{if(a)return;if(n){let f=o++,u=n[f];if(f>=n.length||!l(u)){a=!0;return}let S=!r||r[f]===d.tagName.toLowerCase();if(!S||u!==d.children.length){let w=S&&e[_];typeof w=="string"&&w&&t.set(d,w),h(u)||(a=!0);return}}let g=e[_];typeof g=="string"&&g&&t.set(d,g);let E=d.children;for(let f=0;f<E.length;f++)c(E[f],_===""?String(f):`${_}.${f}`)};return c(s,""),t}function St(s){return e=>{for(let t of s){let i=t(e);if(typeof i=="string"&&i!=="")return i}return null}}var Bt=St([s=>s.getAttribute("data-id"),s=>s.getAttribute("id")]);var Et=/^blockeditor(\.[0-9]+)?$/,rt=s=>[...s.querySelectorAll('script[merge][clay~="live"]')].filter(e=>Et.test(e.getAttribute("merge")));function lt(s,e,t){if(!s.html.includes("data-blockeditor"))return s;let i=u=>W(new DOMParser().parseFromString(u,"text/html")),n=i(s.html),r=n.querySelector("article[data-blockeditor]");if(!r)return s;let l=i(e),o=l.querySelector("article[data-blockeditor]");if(!o)return null;let a=st(n.documentElement,s.identityMap),h=(u,S)=>{let w=n.importNode(S,!0),b=a.get(u);return b&&a.set(w,b),u.replaceWith(w),w},c=h(r,o),d=new Map(rt(n).map(u=>[u.getAttribute("merge"),u]));for(let u of rt(l)){let S=u.getAttribute("merge"),w=d.get(S);w?h(w,u):c.before(n.importNode(u,!0)),d.delete(S)}for(let u of d.values())u.remove();let _=n.querySelector("head > title"),g=l.querySelector("head > title");_&&g?h(_,g):g?n.head.append(n.importNode(g,!0)):_?.remove();let E=n.documentElement,f=t.exportMap(E,u=>{let S=a.get(u);return S?(t.adopt(u,S),u):null});return{...s,html:T(E),identityMap:f}}var wt={"merge-tokens":"tokens","merge-max":"max"};function ot(s,e){if(!e.startsWith("data-")||e==="data-id"||!s?.closest)return null;for(let[t,i]of Object.entries(wt)){let n=s.closest(`[${t}]`);if(n&&n.getAttribute(t).split(/\s+/).includes(e))return i}return null}var m=null,at=null,B=new Map;function V(){return at??=Promise.all([import("./c-O3DPFZCE.js"),import("./c-EJTGYSLN.js")]).then(([s,e])=>{m={...s,beginLineageCapture:e.beginLineageCapture};for(let t of[...B.keys()])B.set(t,s.registerContext(t));return m}),at}var ht=()=>m,I={get size(){return m?m.conflicts.size:0},list(){return m?m.conflicts.list():[]},get(s){return m?m.conflicts.get(s):null},acknowledge(s,e={}){if(m)return m.conflicts.acknowledge(s,e);if(!Array.isArray(s))throw new TypeError("clay.conflicts.acknowledge needs an array of ids");return{removedIds:[],remainingIds:[]}},claim(s,e={}){if(m)return m.conflicts.claim(s,e);if(!e.owner)throw new TypeError("clay.conflicts.claim needs an owner");return{release(){}}},registerContext(s){if(m)return m.conflicts.registerContext(s);if(typeof s!="function")throw new TypeError("clay.conflicts.registerContext needs a resolver function");return B.set(s,null),()=>{let e=B.get(s);B.delete(s),e&&e()}},contextFor(s,e){return m?m.conflicts.contextFor(s,e):null},recoveryOf(s){return m?m.conflicts.recoveryOf(s):null},hasPendingApply(){return m?m.conflicts.hasPendingApply():!1}};var y=null,_e=null;function R(){return _e??=new Promise(s=>setTimeout(s,0)).then(()=>import("./c-HYNM5U6B.js")).then(s=>y=s).catch(s=>{throw _e=null,s}),_e}var ve=null,ge=null;function kt(){return ge??=import("./c-SMLAJASY.js").then(s=>ve=s.presence).catch(s=>{throw ge=null,s}),ge}var ft=te().phase==="entering";$&&!ft&&R().catch(()=>{});$&&V().catch(()=>{});var L=null,ye=null,Q=[];function pt(s){L.presence.update(s),L.editingMarker.update(s)}function xt(){return ye??=Promise.all([import("./c-WOWGGNCJ.js"),import("./c-EEWPNUM2.js")]).then(([s,e])=>{L={presence:s.presence,editingMarker:e.editingMarker};let t=Q;Q=[];for(let i of t)pt(i);return L}).catch(s=>{throw ye=null,s}),ye}function bt(s){if(L){pt(s);return}Q.push(s),xt().catch(()=>{})}var dt=Object.freeze({bytes:null,etag:null,seq:null}),Dt=()=>({msg:"Not in edit mode",msgType:"skipped",code:null,etag:null}),_t=["[editor-ui]",'[clay~="editor-ui"]',"[save-ignore]","[snapshot-remove]","[no-snapshot]","[no-save]","[save-remove]","[freeze] *","[save-freeze] *",'[clay~="no-save"]','[clay~="no-snapshot"]','[clay~="freeze"] *',Ce,He].join(","),gt='[clay~="freeze"], [freeze], [save-freeze]';function X(s){for(let e of s.querySelectorAll(gt))e.replaceChildren();return z()&&Me(s),s}function ut(s){if(s==null)return s;let e=typeof s=="string"?new DOMParser().parseFromString(s,"text/html"):s;return ie(e),W(e),X(e),e}var It=[Ae,'[clay~="no-watch"]',"[no-watch]","[mutations-ignore]"].join(","),Tt=(s,e,t)=>Ie(s,e,t)||Fe.test(e.toLowerCase()),yt=s=>s.getAttribute("data-id")||s.getAttribute("id")||null;function Lt(s){let e=new Map,t=n=>{if(n.matches(_t))return;let r=yt(n);r&&e.set(r,(e.get(r)||0)+1);let l=n.localName==="template"&&n.content?n.content.children:n.children;for(let o of l)t(o)},i=s.nodeType===9?[s.documentElement]:s.nodeType===11?s.children:[s];for(let n of i)n&&n.nodeType===1&&t(n);return e}var Ht=s=>e=>{let t=yt(e);if(!t)return null;let i=e.getRootNode(),n=s.get(i);return n||s.set(i,n=Lt(i)),n.get(t)===1?t:null},me=s=>!!s&&typeof s=="object"&&!Array.isArray(s);function Ft(s){return typeof s!="string"||s===""?null:new DOMParser().parseFromString(s,"text/html").documentElement?.getAttribute("documentetag")||null}function Ct(s,e){e!=="live"||!s||!y||(y.enableContentEditable(s),y.enableOnClick(s),y.enableAdminInputs(s),y.enableAdminResources(s))}function At(){let{forComparison:s,forDirty:e}=Be({flushUndo:!1});return[s,e]}var Rt=1024*1024;function qt(s,e,t,i,n){let r={sender:t,identityMap:i,...n?{etag:n}:{}};return e.length<Rt?JSON.stringify({[s.snapshotKey]:e,...r}):new Blob([`{${JSON.stringify(s.snapshotKey)}:`,JSON.stringify(e),`,${JSON.stringify(r).slice(1)}`],{type:"application/json"})}var Se=class{constructor(){this.sse=null,this.currentFile=null,this.lastHtml=null,this._diskBase=null,this._diskBaseTicket=0,this._agreeSeq=0,this._saveTicket=0,this._peerBaseTicket=0,this._acceptedSaveTicket=0,this._morphTicket=0,this.clientId=this.generateClientId(),this.resumeId=null,this._savedSnapshot?.abort?.abort(),this._savedSnapshot=null,this._sendInFlight=!1,this._queuedSend=null,this._profile=null,this._profilePromise=null,this._sharedData=!1,this._sharedSync=!1,this._workerFeatures=["live-data"],this._liveData=!1,this._editing=null,this._presenceLease=null,this._presenceQuiesced=!1,this._ready=null,this._startGen=0,this.isPaused=!1,this.isDestroyed=!1,this.debug=!1,this._lane=$?"live":"saved",this._laneChosen=!1,this._snapshotHandler=null,this._hiddenHandler=null,this.lastSeenSeq=0,this._pendingHtml=null,this._pendingSeq=null,this._pendingIdentityMap=null,this._pendingEtag=null,this._pendingBy=null,this._morphInFlight=!1,this._rafHandle=null,this._represented=dt,this._handoff=null,this._handoffHeld=!1,this._pendingExternal=null,this._lastExternalSeq=0,this._saveEpoch=0,this._saveSavedHandler=null,this._saveConflictHandler=null,this._settledHandler=null,this._lastIdentityMap=null,this._applyGen=0,this._holdRetryPeer=null,this._holdRetryExt=null,this._servedFetchId=0,this._startupRetry=null,this._engineRetry=null,this._heldLive=!1,this._heldExt=!1,this.identity=it(this.clientId),this.onConnect=null,this.onDisconnect=null,this.onUpdate=null,this.onError=null,this.onNotification=null}get unresolvedConflicts(){return I.list().filter(e=>e.kind!=="apply-incomplete")}_log(e,t=null){if(!this.debug)return;let i=`[LiveSync ${new Date().toISOString()}]`;t!==null?console.log(i,e,t):console.log(i,e)}generateClientId(){return Math.random().toString(36).slice(2,11)+Date.now().toString(36)}generateResumeId(){try{let e=new Uint8Array(16);return(globalThis.crypto||window.crypto).getRandomValues(e),Array.from(e,t=>t.toString(16).padStart(2,"0")).join("")}catch{return Math.random().toString(36).slice(2)+Date.now().toString(36)}}_seedBases(){if(x())return;let e=M({flushUndo:!1});this._setPeerBase(T(e),this.identity.exportMap(e,D),this._ticket()),this._setDiskBase(Y({emitForSync:!1}).forSave,this._ticket())}get lane(){return this._lane}set lane(e){this._lane=e,this._laneChosen=!0}start(e=null,{lane:t,represented:i}={}){if(this.isDestroyed=!1,(this.sse||this._snapshotHandler)&&this.cleanup(),this.currentFile=e||this.detectCurrentFile(),!this.currentFile){console.warn("[LiveSync] No file detected");return}let n=te().mode==="edit"?"live":"saved",r=this.lane==="live";this._lane=t||(this._laneChosen?this._lane:n),this.lastHtml=null,this._lastIdentityMap=null,this._peerBaseTicket=this._ticket(),this._diskBase=null,this._diskBaseTicket=this._ticket(),this._saveTicket=0,this._savedSnapshot?.abort?.abort(),this._savedSnapshot=null,this._applyGen++,this.lastSeenSeq=0,this._lastExternalSeq=0,this._represented=i?Object.freeze({bytes:i.bytes??null,etag:i.etag??null,seq:i.seq??null}):dt,this._acquireEmptySaved=r&&this.lane==="saved"&&this._represented.bytes===null,this._pendingExternal=null,this.resumeId=this.generateResumeId(),this.lane==="live"&&this._seedBases(),this.lane==="live"&&V().catch(()=>{}),console.log(`[LiveSync] Starting for: ${this.currentFile} (lane=${this.lane})`);let l=++this._startGen;if(this.lane==="live"&&!(y&&y.baselineSettled())){let o=this._applyGen,a=this._morphTicket,h=this._saveEpoch,c=this.lastHtml,d=this._diskBaseTicket,_=this._diskBase!==null;this._settledHandler=()=>{if(this._settledHandler=null,!(this.isDestroyed||this._startGen!==l)&&!Pe()&&!x()&&this._saveTicket===0&&this._morphTicket===a){if(c!==null&&this._applyGen===o&&this._saveEpoch===h&&this.lastHtml===c){let g=M({flushUndo:!1});this._setPeerBase(T(g),this.identity.exportMap(g,D),this._ticket())}_&&this._diskBaseTicket===d&&this._setDiskBase(Y({emitForSync:!1}).forSave,this._ticket())}},document.addEventListener("clay:baseline-settled",this._settledHandler,{once:!0})}this._ready=Promise.all([this.lane==="live"?R():null,this._resolveProfile()]).then(()=>{this.isDestroyed||this._startGen!==l||this.connect()}),this.lane==="live"&&(this.listenForSnapshots(),kt().then(o=>this._startPresence(o,l),()=>{}),this._saveSavedHandler=o=>{this._saveEpoch++;let a=this._saveTicket||this._ticket();this._saveTicket=0,a>this._acceptedSaveTicket&&(this._acceptedSaveTicket=a);let h=y?y.getLastSavedBytes():null;h!=null&&this._setDiskBase(h,a),this._relayCommit(o?.detail?.captureId,h)},document.addEventListener("clay:save-saved",this._saveSavedHandler),this._saveConflictHandler=o=>{let a=o.detail&&o.detail.etag;a&&queueMicrotask(()=>{this.isDestroyed||a===se()&&(y?.conflictResolvedBySync(a),this._saveAfterMerge(x(),!1))})},document.addEventListener("clay:save-conflict",this._saveConflictHandler))}cleanup(){if(this.sse&&(this.sse.close(),this.sse=null),de(),Q=[],L&&(L.presence.clear(),L.editingMarker.clear()),this._editing&&(this._editing.stop(),this._editing=null),this._presenceLease){let e=this._presenceLease;this._presenceLease=null,typeof e.stop=="function"?e.stop():e.detach()}this._lastRoster=null,this._snapshotHandler&&(document.removeEventListener("clay:snapshot-ready",this._snapshotHandler),this._snapshotHandler=null),this._saveSavedHandler&&(document.removeEventListener("clay:save-saved",this._saveSavedHandler),this._saveSavedHandler=null),this._saveConflictHandler&&(document.removeEventListener("clay:save-conflict",this._saveConflictHandler),this._saveConflictHandler=null),this._settledHandler&&(document.removeEventListener("clay:baseline-settled",this._settledHandler),this._settledHandler=null),this._hiddenHandler&&(document.removeEventListener("visibilitychange",this._hiddenHandler),document.removeEventListener("clay:workspace-background",this._hiddenHandler),this._hiddenHandler=null),this._queuedSend=null,this._rafHandle!=null&&(this._cancelFrame(this._rafHandle),this._rafHandle=null),this._pendingHtml=null,this._pendingSeq=null,this._pendingIdentityMap=null,this._pendingBy=null,this._pendingExternal=null,this._handoff=null,this._handoffHeld=!1,clearTimeout(this._holdRetryPeer),clearTimeout(this._holdRetryExt),this._holdRetryPeer=null,this._holdRetryExt=null,clearTimeout(this._startupRetry),this._startupRetry=null,clearTimeout(this._engineRetry),this._engineRetry=null,this._servedFetchId++}detectCurrentFile(){let e=window.location.pathname;if(e==="/")return"index.html";e=e.replace(/^\//,"");let t=e.match(/^(.*?\.html(?:clay)?)/);return t?t[1]:e}_resolveProfile(){return this._profilePromise||(this._profilePromise=nt().then(e=>(this._sharedData=e.sharedData,this._sharedSync=e.sharedSync,this._liveData=e.liveData,this._workerFeatures=e.workerFeatures,this._profile=e.profile,this._log(`Wire profile: ${e.profile.name}`),e.profile))),this._profilePromise}_setHeld(e,t,i){let n=e==="live"?"_heldLive":"_heldExt";this[n]!==t&&(this[n]=t,!t&&!this._heldLive&&!this._heldExt&&je({fresh:!0,clearIfMissing:!1}),document.dispatchEvent(new CustomEvent(t?"clay:sync-held":"clay:sync-resumed",{detail:{lane:e,el:i||null}})))}_applyEtagFrame(e){return typeof e.etag!="string"||typeof e.html=="string"?!1:(this._log("Dropping a stamp that arrived with no document: nothing proves this tab holds those bytes"),!0)}connect(){if(this.isDestroyed)return;let t=(this._profile||pe.legacy).streamPath(window.location.href,this.lane,this.resumeId,this.clientId,1);this._hiddenHandler||(this._hiddenHandler=()=>{this._rafHandle==null||!ce()||(this._cancelFrame(this._rafHandle),this._rafHandle=null,this._scheduleNextFrame())},document.addEventListener("visibilitychange",this._hiddenHandler),document.addEventListener("clay:workspace-background",this._hiddenHandler)),this.sse=new Qe(new URL(t,window.location.origin).href,{shared:this._sharedSync,sharedData:this._sharedData,workerFeatures:this._workerFeatures,documentURL:window.location.href,lane:this.lane,clientId:this.clientId,keepAliveWhenHidden:this.lane!=="live"?!1:this._liveData?!0:"shared",startupCheck:!!Le}),this._acquireEmptySaved&&(this._acquireEmptySaved=!1,this._fetchServedDocument(void 0,{repair:!0})),this.sse.onopen=()=>{console.log("[LiveSync] Connected"),this._editing&&this._editing.reconnected(),ve&&ve.reconnect(),this.onConnect&&this.onConnect()},this.sse.addEventListener("open",()=>et({clientId:this.clientId,lane:this.lane})),this.sse.addEventListener("error",()=>de());for(let i of Ze)this.sse.addEventListener(i,n=>{let r;try{r=JSON.parse(n.data)}catch{return}tt(i,r)});this.sse.addEventListener("cursor",i=>{let n;try{n=JSON.parse(i.data)}catch{return}if(!n||n.resync!==!0&&n.startup!==!0)return;let r=n.resync===!0,l=n.startup===!0&&!r;console.log(r?"[LiveSync] Server could not replay everything; refetching the document":"[LiveSync] Checking the served document against the version this tab was served"),this._fetchServedDocument(typeof n.seq=="number"?n.seq:void 0,{repair:r,startup:l})}),this.sse.addEventListener("presence",i=>{let n;try{n=JSON.parse(i.data)}catch{return}bt(n),this._lastRoster=n,this._editing&&this._editing.roster(n)}),this.sse.onmessage=i=>{let n=JSON.parse(i.data),{seq:r}=n;if(typeof r=="number"&&r<=this.lastSeenSeq){this._log(`Dropping stale message: seq=${r}, lastSeen=${this.lastSeenSeq}`);return}if(typeof r=="number"&&(this.lastSeenSeq=r),n.type==="notification"){if(this._maybeAcceptExternalChange(n))return;this.handleNotification(n);return}if(n.error){console.error("[LiveSync] Server error:",n.error),this.onError&&this.onError(new Error(n.error));return}if(this._applyEtagFrame(n))return;let{html:l,sender:o,identityMap:a}=n;if(o===this.clientId){this._log("Ignoring own message (sender matches clientId)");return}if(typeof l!="string"){console.error("[LiveSync] Received invalid html, ignoring");return}let h=typeof n.etag=="string"&&n.etag?n.etag:null;if(this.lane==="live"&&!h&&$e()){this._log(`Dropping an unstamped live frame on a stamping host (seq=${r})`);return}if(this.lane==="saved"&&l===this._represented.bytes){this._represented=Object.freeze({bytes:l,etag:h||null,seq:r??null}),P(h);return}if(h&&l===this.lastHtml){this._log(`Taking the stamp from a peer save of content already applied (seq=${r})`),P(h),y&&y.isSaveConflicted()&&(y.conflictResolvedBySync(h),this._saveAfterMerge(x(),!1));return}this._log(`Received update from: ${o} (my clientId: ${this.clientId}, seq=${r})`),this.applyUpdate(l,r,a,h,n.by),this.onUpdate&&this.onUpdate({html:l,sender:o,seq:r,identityMap:a,etag:h})},this.sse.onerror=()=>{this.sse.readyState===EventSource.CONNECTING?console.log("[LiveSync] Reconnecting..."):this.sse.readyState===EventSource.CLOSED&&(console.log("[LiveSync] Connection closed"),this.onError&&this.onError(new Error("Connection closed"))),this.onDisconnect&&this.onDisconnect()}}listenForSnapshots(){this._snapshotHandler=e=>{if(!e.detail||e.detail.forSave!==!0)return;this._saveTicket=this._ticket();let{documentElement:t}=e.detail;if(!t)return;this._log("snapshot-ready received, preparing to send");let i=this.identity.exportMap(t,D);this._savedSnapshot?.abort?.abort();let n={html:null,steps:null,copy:null,abort:null,identityMap:i,ticket:this._saveTicket,captureId:e.detail.captureId??null};if(e.detail.sliced){n.copy=Oe(t),n.abort=new AbortController;let{signal:r}=n.abort;Promise.all([import("./c-L6KVXPBO.js"),import("./c-ZWBI4HYK.js")]).then(([{serializeForSyncSteps:l},{runSliced:o}])=>{if(r.aborted||typeof n.html=="string")return;let a=(function*(){let h=yield*l(n.copy);n.html=h,n.steps=null})();return n.steps=a,o(a,{signal:r})}).catch(()=>{})}else n.html=T(t);this._savedSnapshot=n},document.addEventListener("clay:snapshot-ready",this._snapshotHandler)}_enqueueSend(e,t,i=null){if(!this._profile){this._queuedSend={html:e,identityMap:t,etag:i},this._resolveProfile().then(()=>this._flushQueuedSend());return}if(this._sendInFlight){this._queuedSend={html:e,identityMap:t,etag:i};return}this._postUpdate(e,t,i)}_flushQueuedSend(){if(this.isDestroyed||this._sendInFlight)return;let e=this._queuedSend;e&&(this._queuedSend=null,this._postUpdate(e.html,e.identityMap,e.etag))}_relayCommit(e=null,t=null){if(this.isDestroyed)return;let i=se(),n=this._savedSnapshot;if(n&&(n.captureId??null)!==(e??null)){this._log("Skipping send - snapshot belongs to a different capture");return}let r=n;if(this._savedSnapshot=null,r?.copy&&typeof r.html!="string")if(r.abort?.abort(),r.steps)for(let c=r.steps.next();!c.done;c=r.steps.next());else r.html=T(r.copy);if(!r||typeof r.html!="string")return;let l=r;if(typeof t=="string"){try{l=lt(r,t,this.identity)}catch(c){this._log(`Skipping send - page relay failed: ${c&&c.message}`);return}if(!l){this._log("Skipping send - accepted capture has no page article");return}}let o=Mt(e)??t,a=typeof o=="string"?We(o):0;l={...l,html:Pt(Ye(l.html,Math.max(Je(),a)),o)};let h=!i&&l.html===this.lastHtml;if(this._setPeerBase(l.html,l.identityMap,l.ticket),h){this._log("Skipping send - HTML unchanged");return}this._enqueueSend(l.html,l.identityMap,i||null)}_postUpdate(e,t,i=null,n=0){this._log(`Sending update (HTML length: ${e.length})`),this._sendInFlight=!0;let r=!1,l=this._profile||pe.legacy;fetch(new URL(l.relayPath,window.location.origin).href,{method:"POST",headers:{"Content-Type":"application/json",[l.documentHeader]:window.location.href,[ze]:String(1)},body:qt(l,e,this.clientId,t,i)}).then(o=>{o.ok||(console.warn("[LiveSync] Save returned status:",o.status),r=o.status>=500||o.status===408||o.status===429)}).catch(o=>{console.error("[LiveSync] Save failed:",o),this.onError&&this.onError(o),r=!0}).finally(()=>{this._sendInFlight=!1;let o=this._queuedSend;if(this._queuedSend=null,o){this._postUpdate(o.html,o.identityMap,o.etag);return}!r||!i||n>=3||this.isDestroyed||(this._sendInFlight=!0,setTimeout(()=>{if(this._sendInFlight=!1,this.isDestroyed)return;let a=this._queuedSend;this._queuedSend=null,a?this._postUpdate(a.html,a.identityMap,a.etag):this._postUpdate(e,t,i,n+1)},500*2**n))})}_startPresence(e,t){return this.isDestroyed||this._startGen!==t||this._presenceLease?null:e.connect({send:i=>this._postPresence(i)}).then(i=>this.isDestroyed||this._startGen!==t?(typeof i?.stop=="function"?i.stop():i?.detach(),null):(this._presenceLease=i||null,Ue("presence-editing")),()=>!1).then(i=>{if(!(!i||this.isDestroyed||this._startGen!==t||this._editing))return R().then(n=>{this.isDestroyed||this._startGen!==t||this._editing||(this._editing=new n.EditingSender({}),this._presenceQuiesced?this._editing.stop():(this._editing.start(),this._editing.roster(this._lastRoster)))})})}_postPresence(e){let t=new URL("/_/presence",window.location.origin);return t.searchParams.set("client-id",this.clientId),fetch(t.href,{method:"POST",headers:{"Content-Type":"application/json","Document-URL":window.location.href},body:JSON.stringify(e)}).then(i=>i.ok,()=>!1)}_maybeAcceptExternalChange(e){if(this.lane!=="live")return!1;let t=e.data;return t&&t.kind==="external-change"?(typeof t.html=="string"?this._enqueueExternal(t.html,e.seq,t.etag,t.by):this._fetchExternalChange(e.seq),!0):typeof e.msg=="string"&&e.msg.endsWith("changed on disk outside this tab")?(this._fetchExternalChange(e.seq),!0):!1}_enqueueExternal(e,t,i,n){if(typeof t=="number"){if(t<=this._lastExternalSeq){this._log(`Dropping replayed external change: seq=${t}`);return}this._lastExternalSeq=t}this._pendingExternal={html:e,seq:t,saveEpoch:this._saveEpoch,etag:typeof i=="string"&&i?i:null,by:n??null},this._scheduleNextFrame()}_fetchExternalChange(e){if(typeof e=="number"){if(e<=this._lastExternalSeq)return;this._lastExternalSeq=e}this._fetchServedDocument(e)}_fetchServedDocument(e,{attempt:t=0,repair:i=!1,startup:n=!1}={}){if(n&&(this._morphInFlight||this._pendingHtml!=null||this._pendingExternal!=null)){this._retryStartup(t);return}let r=this._saveEpoch,l=this._startGen,o=this.lastSeenSeq,a=this._applyGen,h=++this._servedFetchId;i&&typeof e!="number"&&(e=this._lastExternalSeq),fetch(new URL(window.location.href),{cache:"no-store"}).then(c=>c.ok?c.text():null).then(c=>{if(this.isDestroyed||c==null||this._startGen!==l)return;let d=Ft(c),_=n||i&&!!d;if(_){if(h!==this._servedFetchId){i&&this._retryStartup(t+1,{repair:i,startup:n},typeof e=="number"?Math.max(e,this._lastExternalSeq):e);return}if(r!==this._saveEpoch||o!==this.lastSeenSeq||a!==this._applyGen||this._morphInFlight||this._pendingHtml!=null||this._pendingExternal!=null){this._retryStartup(t+1,{repair:i,startup:n},e);return}if(d){if(d===re()){(i||n)&&this.lane==="saved"&&(this._represented=Object.freeze({bytes:c,etag:d,seq:e??null}));return}}else if(!i)return}else if(typeof e=="number"&&e<this._lastExternalSeq){i&&t<3&&this._fetchServedDocument(this._lastExternalSeq,{attempt:t+1,repair:i,startup:n});return}else if(this._saveEpoch>r){t<3&&(console.log("[LiveSync] Refetching external change: own save landed mid-fetch"),this._fetchServedDocument(e,{attempt:t+1,repair:i,startup:n}));return}this._pendingExternal={html:c,seq:e,saveEpoch:r,etag:d,by:null,fetchOptions:{repair:i,startup:n,attempt:t,versionCheck:_},startGen:l,seenSeq:o,applyGen:a,fetchId:h},this._scheduleNextFrame()}).catch(c=>{this._log("External-change fetch failed",c),typeof e=="number"&&this._lastExternalSeq===e&&(this._lastExternalSeq=e-1)})}_retryStartup(e=0,t={startup:!0},i){this.isDestroyed||e>3||(clearTimeout(this._startupRetry),this._startupRetry=setTimeout(()=>{if(this._startupRetry=null,!this.isDestroyed){if(this._morphInFlight||this._pendingHtml!=null||this._pendingExternal!=null){this._retryStartup(e,t,i);return}this._fetchServedDocument(i,{...t,attempt:e})}},16))}applyUpdate(e,t,i,n,r){this.isDestroyed||(this._pendingHtml=e,this._pendingSeq=t,this._pendingIdentityMap=i,this._pendingEtag=n??null,this._pendingBy=r??null,this._scheduleNextFrame())}prepareHandoff(e={}){return this._handoff?Promise.resolve(this._handoff):this.isDestroyed?Promise.reject(new Error("LiveSync: stopped")):(this._handoffHeld=!0,this._rafHandle!=null&&(this._cancelFrame(this._rafHandle),this._rafHandle=null),import("./c-DAUQ6JZ3.js").then(({engineHandoff:t})=>t(this,e)).then(t=>this._handoff=t))}_scheduleNextFrame(){this.isDestroyed||this._handoffHeld||this._rafHandle==null&&(this._morphInFlight||(this._rafHandle=this._requestFrame(()=>this._runPending())))}async _runPending(){if(this._rafHandle=null,this.isDestroyed)return;if(!A()){this._morphInFlight=!0;try{await J()}catch(r){this._log("Merge engine failed to load",r),this._engineRetry=setTimeout(()=>this._scheduleNextFrame(),2e3);return}finally{this._morphInFlight=!1}if(this.isDestroyed)return}let e=this._pendingExternal,t=e?.fetchOptions||null,i=!!(t?.startup||t?.versionCheck),n=!1;if(e!=null&&(i&&this._pendingHtml!=null?n=!1:this._pendingHtml==null?n=!0:n=!(typeof e.seq=="number"&&typeof this._pendingSeq=="number"&&this._pendingSeq<e.seq)),n)if(this._pendingExternal=null,typeof e.seq=="number"&&e.seq<this._lastExternalSeq)i&&t?.repair?this._retryStartup((t.attempt||0)+1,t,this._lastExternalSeq):this._log("Dropping superseded external change at drain");else if(i&&e.startGen!==this._startGen)this._log("Dropping superseded startup check at drain");else if(i&&e.fetchId!==this._servedFetchId)t?.repair?this._retryStartup((t.attempt||0)+1,t,this._lastExternalSeq):this._log("Dropping superseded startup check at drain");else if(i&&(e.saveEpoch!==this._saveEpoch||e.seenSeq!==this.lastSeenSeq||e.applyGen!==this._applyGen))this._retryStartup((t.attempt||0)+1,t,e.seq);else if(i&&e.etag!=null&&e.etag===re())this._log("Startup check found the same version: nothing to apply");else if(!i&&e.saveEpoch!==this._saveEpoch)console.log("[LiveSync] Refetching external change: own save landed after queue"),this._fetchServedDocument(e.seq,t||{});else{this._morphInFlight=!0;try{await this._doApplyExternal(e.html,e.seq,e.etag,e.by,t)}catch(r){console.error("[LiveSync] applyExternal failed:",r)}finally{this._morphInFlight=!1}}else{let r=this._pendingHtml,l=this._pendingSeq,o=this._pendingIdentityMap,a=this._pendingEtag,h=this._pendingBy;if(this._pendingHtml=null,this._pendingSeq=null,this._pendingIdentityMap=null,this._pendingEtag=null,this._pendingBy=null,r==null)return;this._morphInFlight=!0;try{await this._doApplyUpdate(r,l,o,a,h)}catch(c){console.error("[LiveSync] applyUpdate failed:",c)}finally{this._morphInFlight=!1}}!this.isDestroyed&&(this._pendingHtml!=null||this._pendingExternal!=null)&&this._scheduleNextFrame()}_ticket(){return++this._agreeSeq}_setDiskBase(e,t){t<=this._diskBaseTicket||(this._diskBase=e,this._diskBaseTicket=t)}_setPeerBase(e,t,i){return i<this._peerBaseTicket?!1:(this._peerBaseTicket=i,this.lastHtml=e,this._lastIdentityMap=t&&typeof t=="object"&&!Array.isArray(t)?t:null,!0)}_requestFrame(e){if(ce()){if(typeof MessageChannel=="function"){let n=new MessageChannel,r=!0;return n.port1.onmessage=()=>{n.port1.close(),r&&e()},n.port2.postMessage(null),{cancel:()=>{r=!1,n.port1.close()}}}let i=setTimeout(e,0);return{cancel:()=>clearTimeout(i)}}if(typeof window<"u"&&typeof window.requestAnimationFrame=="function"){let i=window.requestAnimationFrame(e);return{cancel:()=>window.cancelAnimationFrame?.(i)}}let t=setTimeout(e,16);return{cancel:()=>clearTimeout(t)}}_cancelFrame(e){e?.cancel?.()}async _mergeIncoming(e,t,{base:i,baseIdentityMap:n=null,captureLocal:r,synthetic:l,source:o="peer",seq:a=null,etag:h=null,extra:c={}}){A()||await J();let{beginApply:d,completeApply:_,failApply:g,beginLineageCapture:E}=ht()||await V(),f,u=p=>{f=E(I.list());try{let v=A().HyperMorph.mergeDocument({...p,lineage:f.lineage});return f.returned(),v}catch(v){throw f.invalidate(I.list()),f.finish(I.list()),v}},S=this.identity,w=Te,b=Ht(new WeakMap),N=p=>me(p)?{first:b,map:p,then:b}:b,O=l?p=>b(p)||S.idOf(D(p)||p)||null:b,H=e,we=new Map;if(me(t)){H=new DOMParser().parseFromString(e,"text/html"),ie(H);let p=A().HyperMorph.importMap(H.documentElement,t);for(let v of H.querySelectorAll("[data-id], [id]")){let k=b(v),De=k&&p.get(v);De&&we.set(k,De)}}H=ut(H);let Z={live:document,remote:H,ignore:p=>p.matches(_t),opaque:p=>p.matches(gt)||z()&&qe(p),remoteWins:p=>p.matches(It),ignoreAttribute:(p,v,k)=>Tt(p,v,k)||z()&&Re(k||p,v),attributeMerge:ot,protectFocusedValue:!0,restoreFocus:document.activeElement?.localName!=="clay-editor-ui",keepLiveOnly:o==="peer",scripts:{mergeTags:A().mergeTagRecognizers},hooks:{beforeAttributeUpdated:w},...c},ke=this.lane==="live"&&x(),xe=he(),G,ee=null,C=null;if(i==null)G=u({...Z,base:null,local:{root:X(r()),toLive:D},identity:{local:O,remote:N(t)}});else if(ke){let p=r(),v=p.cloneNode(!0);ee=X(p),C=d({source:o,seq:a,etag:h,root:ee,recoveryRoot:v,domain:o==="disk"?"save":"sync"});try{G=u({...Z,base:ut(i),local:{root:ee,toLive:D},identity:{base:N(n),local:O,remote:N(t)}})}catch(k){throw g(C,k),k}}else{let p=X(r()),v=p.ownerDocument;v.documentElement!==p&&v.replaceChild(p,v.documentElement),G=u({...Z,base:v,local:{root:p,toLive:D},fastPath:!0,identity:{base:O,local:O,remote:N(t)}})}let U=this._ticket();this._morphTicket=U;let F;try{F=await G}catch(p){f.invalidate(I.list());try{C&&g(C,p,{ticket:U})}finally{f.finish(I.list())}throw p}let mt=he().gen!==xe.gen,be;try{f.prepareComplete(F,I.list()),be=C?_(C,F.conflicts,{ticket:U}):[]}finally{f.finish(I.list())}this.lane==="live"&&F.localDiverged?Ve():ke&&!F.localDiverged&&Xe(xe);let vt=new Set(me(t)?Object.values(t):[]);for(let[p,v]of F.identities){let k=vt.has(v)?v:we.get(v);k&&S.adopt(p,k)}return{report:F,ticket:U,typedDuringWait:mt,conflictIds:be}}_saveAfterMerge(e,t){(e||t)&&Ke()&&(y?y.savePageThrottled():Dt())}async _doApplyUpdate(e,t,i,n,r=null){if(ue(e)){await fe();return}this.lane==="live"&&!y&&await R(),this._log("applyUpdate - pausing mutations and morphing"),this.isPaused=!0;let l=window.scrollX,o=window.scrollY;q.pause(),oe();let a=!1,h=!1,c=!1;try{if(this.lane==="live"&&x()&&this.lastHtml===null){console.log("[LiveSync] Holding incoming update: unsaved local edits and no baseline to merge against"),this._setHeld("live",!0,null);let f=this._saveEpoch,u=this.lastSeenSeq;clearTimeout(this._holdRetryPeer),this._holdRetryPeer=setTimeout(()=>{this._holdRetryPeer=null,!(this.isDestroyed||this._pendingHtml!=null)&&this._saveEpoch===f&&this.lastSeenSeq===u&&(this._pendingHtml=e,this._pendingSeq=t,this._pendingIdentityMap=i,this._pendingBy=r,this._scheduleNextFrame())},3e3);return}this._setHeld("live",!1);let{report:d,ticket:_,typedDuringWait:g,conflictIds:E}=await this._mergeIncoming(e,i,{base:this.lastHtml,baseIdentityMap:this._lastIdentityMap,captureLocal:()=>M({flushUndo:!1}),synthetic:!0,source:"peer",seq:t,etag:n});if(a=d.localDiverged,h=g,ne(),window.scrollTo(l,o),this._setPeerBase(e,i,_),this._applyGen++,this._acceptedSaveTicket>_||(typeof n=="string"&&n?(P(n),c=!0):le()),this.lane==="live"&&!a&&!x()){let{forSave:f,forComparison:u,forDirty:S}=Y({emitForSync:!1,flushUndo:!1});y.setLastSavedBaselines(u,S),this._setDiskBase(f,this._ticket())}this.lane==="saved"&&(this._represented=Object.freeze({bytes:e,etag:n||null,seq:t??null})),document.dispatchEvent(new CustomEvent("clay:sync-applied",{detail:{seq:t,source:this.lane==="saved"?"disk":"peer",lane:this.lane,by:r||null,html:e,report:d,unresolved:this.unresolvedConflicts,conflictIds:E}}))}finally{this._log("applyUpdate - morph complete, resuming mutations"),q.resume(),ae(),await new Promise(d=>setTimeout(d,0)),this.isPaused=!1}c&&y?.conflictResolvedBySync(n),this._saveAfterMerge(a,h)}async _doApplyExternal(e,t,i=null,n=null,r=null){if(ue(e)){await fe();return}this.lane==="live"&&!y&&await R();let l=this._diskBase;this._log(`applyExternal - external disk change (seq=${t})`),this.isPaused=!0;let o=window.scrollX,a=window.scrollY;q.pause(),oe();let h=!1,c=!1,d=!1;try{if(this.lane==="live"&&x()&&this._diskBase===null){if(console.log("[LiveSync] Holding external change: unsaved local edits and no baseline to merge against"),this._setHeld("external",!0,null),r?.startup||r?.versionCheck){clearTimeout(this._holdRetryExt),this._holdRetryExt=setTimeout(()=>{if(this._holdRetryExt=null,this.isDestroyed)return;let S=typeof t=="number"?Math.max(t,this._lastExternalSeq):t;this._retryStartup(0,r,S)},3e3);return}let u=this._saveEpoch;clearTimeout(this._holdRetryExt),this._holdRetryExt=setTimeout(()=>{this._holdRetryExt=null,!(this.isDestroyed||this._pendingExternal!=null)&&(this._pendingExternal={html:e,seq:t,saveEpoch:u,etag:i,by:n},this._scheduleNextFrame())},3e3);return}this._setHeld("external",!1);let{report:_,ticket:g,typedDuringWait:E,conflictIds:f}=await this._mergeIncoming(e,null,{base:this._diskBase,captureLocal:()=>Ne().saveClone,synthetic:!1,source:"disk",seq:t,etag:i,extra:{beforeApply:u=>Ct(u.documentElement,this.lane)}});if(h=_.localDiverged,c=E,ne(),this._setDiskBase(e,g),window.scrollTo(o,a),this._acceptedSaveTicket>g||(typeof i=="string"&&i?(P(i),d=!0):le()),this.lane==="live"&&!h&&!x()){y.setLastSavedBaselines(...At()),y.setUnsavedChanges(!1);let u=M({flushUndo:!1});this._setPeerBase(T(u),this.identity.exportMap(u,D),g),this._applyGen++}this.lane==="saved"&&(this._represented=Object.freeze({bytes:e,etag:i||null,seq:t??null})),document.dispatchEvent(new CustomEvent("clay:sync-applied",{detail:{seq:t,source:"disk",etag:typeof i=="string"&&i?i:null,by:n||null,html:e,report:_,conflictIds:f,unresolved:this.unresolvedConflicts}}))}finally{this._log("applyExternal - morph complete, resuming mutations"),q.resume(),ae(),await new Promise(_=>setTimeout(_,0)),this.isPaused=!1}d&&y?.conflictResolvedBySync(i),this._saveAfterMerge(h,c),l!=null&&j(l)===j(e)&&y&&y.schedulePaintHeal(()=>this._diskBase==null?null:j(this._diskBase))}handleNotification({msgType:e,msg:t,action:i,persistent:n,data:r}){this._log(`Notification received: ${e} - ${t}`),e==="data-loss"||(n&&window.toastPersistent?window.toastPersistent(t,e):window.toast?window.toast(t,e):console.log(`[LiveSync] Notification: ${t}`)),document.dispatchEvent(new CustomEvent("clay:notification",{detail:{msgType:e,msg:t,action:i,persistent:n,data:r}})),this.onNotification&&this.onNotification({msgType:e,msg:t,action:i,persistent:n,data:r})}stop(){this.cleanup(),this.isDestroyed=!0,console.log("[LiveSync] Stopped")}editParticipant(){return R().then(e=>e.syncParticipant(this))}},K=null;Ge((s,e)=>{K=typeof s=="string"?{html:s,captureId:e?.captureId??null}:null});var Mt=s=>K&&(K.captureId??null)===(s??null)?K.html:null;function Pt(s,e){if(typeof e!="string"||e===s)return s;let t=i=>new DOMParser().parseFromString(i,"text/html").documentElement;return t(s).isEqualNode(t(e))?e:s}var Ee=new Se;typeof window<"u"&&!ft&&(document.readyState==="loading"?document.addEventListener("DOMContentLoaded",()=>Ee.start()):Ee.start());var yn=(s,e,t)=>J().then(({morph:i})=>i(s,e,t));var mn=Ee;export{I as a,_t as b,It as c,Se as d,Ee as e,yn as f,mn as g};