@inkandswitch/patchwork-pkg-base 0.0.41 → 0.0.42

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 (51) hide show
  1. package/package.json +1 -1
  2. package/packages/account/dist/{InlineContactAvatar-TVDLE2AV.js → InlineContactAvatar-6UAAJ73R.js} +2 -1
  3. package/packages/account/dist/InlineContactAvatar-6UAAJ73R.js.map +7 -0
  4. package/packages/account/dist/index.js +1 -1
  5. package/packages/chat/dist/{ChatRoot-DQkQQvPO.js → ChatRoot-WdCS4aLg.js} +3001 -2807
  6. package/packages/chat/dist/{context-tool-BuY29sJF.js → context-tool-KkwLFr2k.js} +2 -2
  7. package/packages/chat/dist/{index-NiYOPQzY.js → index-P8UuZ1jj.js} +3 -3
  8. package/packages/chat/dist/index.js +1 -1
  9. package/packages/chat/dist/{tool-He3mRTzT.js → tool-DWqQOW59.js} +2 -2
  10. package/packages/chat/package.json +1 -1
  11. package/packages/codemirror-base/dist/chunk-H5POLO74.js +2658 -0
  12. package/packages/codemirror-base/dist/chunk-H5POLO74.js.map +7 -0
  13. package/packages/codemirror-base/dist/lib/codemirror.js +1 -1
  14. package/packages/codemirror-base/dist/tool.js +9 -5
  15. package/packages/codemirror-base/dist/tool.js.map +2 -2
  16. package/packages/codemirror-base/package.json +1 -1
  17. package/packages/codemirror-markdown/dist/extensions/markdown.js +0 -94
  18. package/packages/codemirror-markdown/dist/extensions/markdown.js.map +3 -3
  19. package/packages/drafts/dist/assets/{DraftOverlayProvider-B0Mtq1UF.js → DraftOverlayProvider-VYKlzOy1.js} +85 -25
  20. package/packages/drafts/dist/assets/DraftOverlayProvider-VYKlzOy1.js.map +1 -0
  21. package/packages/drafts/dist/assets/{DraftListProvider-D41q9O5B.js → DraftStateProvider-DX8b_RgQ.js} +270 -39
  22. package/packages/drafts/dist/assets/DraftStateProvider-DX8b_RgQ.js.map +1 -0
  23. package/packages/drafts/dist/assets/{clone-policy-C4pVv5sr.js → clone-policy-CIdEhg7W.js} +5 -2
  24. package/packages/drafts/dist/assets/{clone-policy-C4pVv5sr.js.map → clone-policy-CIdEhg7W.js.map} +1 -1
  25. package/packages/drafts/dist/assets/draft-docs-Bi8K1y3a.js +554 -0
  26. package/packages/drafts/dist/assets/draft-docs-Bi8K1y3a.js.map +1 -0
  27. package/packages/drafts/dist/assets/main-BR_tW0sp.js +3105 -0
  28. package/packages/drafts/dist/assets/main-BR_tW0sp.js.map +1 -0
  29. package/packages/drafts/dist/index.js +14 -14
  30. package/packages/drafts/dist/index.js.map +1 -1
  31. package/packages/drafts/package.json +1 -1
  32. package/packages/threepane/dist/IsolatedDocumentArea-2JIBFDOX.js +2 -0
  33. package/packages/threepane/dist/{PatchworkFrame-4AEBOBRE.js → PatchworkFrame-P5P6JTJE.js} +2 -2
  34. package/packages/threepane/dist/chunk-CGHYPNSB.js +972 -0
  35. package/packages/threepane/dist/chunk-CGHYPNSB.js.map +7 -0
  36. package/packages/threepane/dist/index.js +1 -1
  37. package/packages/tldraw4/dist/tool.js +61 -10
  38. package/packages/tldraw4/dist/tool.js.map +2 -2
  39. package/packages/tldraw4/package.json +2 -2
  40. package/packages/account/dist/InlineContactAvatar-TVDLE2AV.js.map +0 -7
  41. package/packages/codemirror-base/dist/chunk-5IG5TKXV.js +0 -795
  42. package/packages/codemirror-base/dist/chunk-5IG5TKXV.js.map +0 -7
  43. package/packages/drafts/dist/assets/DraftListProvider-D41q9O5B.js.map +0 -1
  44. package/packages/drafts/dist/assets/DraftOverlayProvider-B0Mtq1UF.js.map +0 -1
  45. package/packages/drafts/dist/assets/main-Cp8VsNHh.js +0 -2331
  46. package/packages/drafts/dist/assets/main-Cp8VsNHh.js.map +0 -1
  47. package/packages/threepane/dist/IsolatedDocumentArea-MZBBRFMY.js +0 -2
  48. package/packages/threepane/dist/chunk-HNLVJAZZ.js +0 -972
  49. package/packages/threepane/dist/chunk-HNLVJAZZ.js.map +0 -7
  50. /package/packages/threepane/dist/{IsolatedDocumentArea-MZBBRFMY.js.map → IsolatedDocumentArea-2JIBFDOX.js.map} +0 -0
  51. /package/packages/threepane/dist/{PatchworkFrame-4AEBOBRE.js.map → PatchworkFrame-P5P6JTJE.js.map} +0 -0
@@ -0,0 +1,554 @@
1
+ import { encodeHeads, decodeHeads, isValidAutomergeUrl } from "@automerge/automerge-repo/slim";
2
+ import * as Automerge from "@automerge/automerge/slim";
3
+ const CHANGE_GROUP_DOC_VERSION = 1;
4
+ const INACTIVITY_GAP_MS = 10 * 60 * 1e3;
5
+ const SLICE_BUDGET_MS = 8;
6
+ const GROUPING_DEBOUNCE_MS = 250;
7
+ const LOCAL_PATCH_SOURCES = /* @__PURE__ */ new Set(["change", "changeAt", "emptyChange"]);
8
+ async function ensureChangeGroupDoc(repo, draftHandle) {
9
+ const draft = draftHandle.doc();
10
+ const existingUrl = getChangeGroupDocUrl(draft);
11
+ if (existingUrl && isValidAutomergeUrl(existingUrl)) {
12
+ const handle = await repo.find(existingUrl);
13
+ const doc = handle.doc();
14
+ if (doc && (doc.version !== CHANGE_GROUP_DOC_VERSION || doc.inactivityGapMs !== INACTIVITY_GAP_MS)) {
15
+ handle.change((d) => {
16
+ d.version = CHANGE_GROUP_DOC_VERSION;
17
+ d.inactivityGapMs = INACTIVITY_GAP_MS;
18
+ d.groups = {};
19
+ d.computedThrough = {};
20
+ });
21
+ }
22
+ if (!draft?.changeGroupDocUrl) {
23
+ draftHandle.change((d) => {
24
+ if (!d.changeGroupDocUrl) d.changeGroupDocUrl = existingUrl;
25
+ });
26
+ }
27
+ return handle;
28
+ }
29
+ const changeGroupHandle = repo.create({
30
+ "@patchwork": { type: "change-group" },
31
+ version: CHANGE_GROUP_DOC_VERSION,
32
+ inactivityGapMs: INACTIVITY_GAP_MS,
33
+ groups: {},
34
+ computedThrough: {}
35
+ });
36
+ draftHandle.change((d) => {
37
+ if (!d.changeGroupDocUrl) d.changeGroupDocUrl = changeGroupHandle.url;
38
+ });
39
+ const settled = draftHandle.doc()?.changeGroupDocUrl;
40
+ if (settled && settled !== changeGroupHandle.url && isValidAutomergeUrl(settled)) {
41
+ return repo.find(settled);
42
+ }
43
+ return changeGroupHandle;
44
+ }
45
+ function getChangeGroupDocUrl(draft) {
46
+ const legacyDraft = draft;
47
+ return draft?.changeGroupDocUrl ?? legacyDraft?.changeGroupCacheUrl;
48
+ }
49
+ function computeEditCounts(doc, hash, deps) {
50
+ try {
51
+ return countPatches(
52
+ Automerge.diff(
53
+ doc,
54
+ deps,
55
+ [hash]
56
+ )
57
+ );
58
+ } catch (err) {
59
+ console.warn("[drafts] failed to diff change for edit counts:", hash, err);
60
+ return { additions: 0, deletions: 0 };
61
+ }
62
+ }
63
+ function computeRangeEditCounts(doc, from, to) {
64
+ try {
65
+ return countPatches(
66
+ Automerge.diff(
67
+ doc,
68
+ from,
69
+ to
70
+ )
71
+ );
72
+ } catch (err) {
73
+ console.warn("[drafts] failed to diff range for edit counts:", err);
74
+ return { additions: 0, deletions: 0 };
75
+ }
76
+ }
77
+ function countPatches(patches) {
78
+ let additions = 0;
79
+ let deletions = 0;
80
+ for (const patch of patches) {
81
+ if (patch.path[0] === "@patchwork") continue;
82
+ if (patch.action === "splice") {
83
+ additions += patch.value.length;
84
+ } else if (patch.action === "insert") {
85
+ additions += Array.isArray(patch.values) ? patch.values.length : 1;
86
+ } else if (patch.action === "del") {
87
+ deletions += patch.length ?? 1;
88
+ } else {
89
+ additions += 1;
90
+ }
91
+ }
92
+ return { additions, deletions };
93
+ }
94
+ async function getDocCreationTime(repo, url) {
95
+ if (!url) return void 0;
96
+ try {
97
+ const handle = await repo.find(url);
98
+ const doc = handle.doc();
99
+ if (!doc) return void 0;
100
+ const metas = Automerge.getChangesMetaSince(doc, []);
101
+ return metas[0]?.time || void 0;
102
+ } catch (err) {
103
+ console.warn("[drafts] failed to resolve creation time for:", url, err);
104
+ return void 0;
105
+ }
106
+ }
107
+ function newestFirst(a, b) {
108
+ return b.time - a.time || b.seq - a.seq;
109
+ }
110
+ function splitIntoGroups(rowsNewestFirst) {
111
+ const groups = [];
112
+ let window = [];
113
+ let prevTimeMs = null;
114
+ for (const row of rowsNewestFirst) {
115
+ const timeMs = row.time * 1e3;
116
+ if (prevTimeMs !== null && prevTimeMs - timeMs > INACTIVITY_GAP_MS && window.length > 0) {
117
+ groups.push(window);
118
+ window = [];
119
+ }
120
+ window.push(row);
121
+ prevTimeMs = timeMs;
122
+ }
123
+ if (window.length > 0) groups.push(window);
124
+ return groups;
125
+ }
126
+ function groupId(rowsNewestFirst) {
127
+ return `tg-${rowsNewestFirst[0].hash}`;
128
+ }
129
+ function collectMemberRows(out, member, doc, since, createdAt) {
130
+ let metas;
131
+ try {
132
+ metas = Automerge.getChangesMetaSince(doc, since);
133
+ } catch (err) {
134
+ console.warn(
135
+ "[drafts] change grouping: failed to read changes for member:",
136
+ member.url,
137
+ err
138
+ );
139
+ return;
140
+ }
141
+ metas.forEach((meta, seq) => {
142
+ if (createdAt !== void 0 && meta.time && meta.time < createdAt) return;
143
+ out.push({
144
+ memberUrl: member.url,
145
+ doc,
146
+ hash: meta.hash,
147
+ deps: meta.deps,
148
+ time: meta.time,
149
+ actor: meta.actor,
150
+ seq
151
+ });
152
+ });
153
+ }
154
+ function dedupedActors(rowsNewestFirst) {
155
+ const actors = [];
156
+ for (const row of rowsNewestFirst) {
157
+ if (!actors.includes(row.actor)) actors.push(row.actor);
158
+ }
159
+ return actors;
160
+ }
161
+ function idle() {
162
+ return new Promise((resolve) => {
163
+ if (typeof requestIdleCallback === "function") {
164
+ requestIdleCallback(() => resolve());
165
+ } else {
166
+ setTimeout(resolve, 0);
167
+ }
168
+ });
169
+ }
170
+ function createSlicer(isAborted, onYield) {
171
+ let sliceStart = performance.now();
172
+ return {
173
+ async tick() {
174
+ if (isAborted()) return false;
175
+ if (performance.now() - sliceStart < SLICE_BUDGET_MS) return true;
176
+ onYield();
177
+ await idle();
178
+ sliceStart = performance.now();
179
+ return !isAborted();
180
+ }
181
+ };
182
+ }
183
+ async function buildGroup(rowsNewestFirst, slicer) {
184
+ let additions = 0;
185
+ let deletions = 0;
186
+ for (const row of rowsNewestFirst) {
187
+ if (!await slicer.tick()) return null;
188
+ const counts = computeEditCounts(row.doc, row.hash, row.deps);
189
+ additions += counts.additions;
190
+ deletions += counts.deletions;
191
+ }
192
+ const newest = rowsNewestFirst[0];
193
+ const oldest = rowsNewestFirst[rowsNewestFirst.length - 1];
194
+ return {
195
+ id: groupId(rowsNewestFirst),
196
+ startTime: oldest.time,
197
+ endTime: newest.time,
198
+ newestMemberUrl: newest.memberUrl,
199
+ newestHash: newest.hash,
200
+ actors: dedupedActors(rowsNewestFirst),
201
+ additions,
202
+ deletions,
203
+ changeCount: rowsNewestFirst.length
204
+ };
205
+ }
206
+ function byMemberUrl(a, b) {
207
+ return a.url < b.url ? -1 : a.url > b.url ? 1 : 0;
208
+ }
209
+ function sameHeads(a, b) {
210
+ if (a === b) return true;
211
+ if (!a || !b || a.length !== b.length) return false;
212
+ const set = new Set(b);
213
+ return a.every((h) => set.has(h));
214
+ }
215
+ function createChangeGrouper(repo, options = {}) {
216
+ const tasks = /* @__PURE__ */ new Map();
217
+ const queue = [];
218
+ let running = false;
219
+ let disposed = false;
220
+ const creationTimes = /* @__PURE__ */ new Map();
221
+ const creationTime = (url) => {
222
+ let cached = creationTimes.get(url);
223
+ if (!cached) {
224
+ cached = getDocCreationTime(repo, url);
225
+ creationTimes.set(url, cached);
226
+ }
227
+ return cached;
228
+ };
229
+ function setTimelines(specs) {
230
+ if (disposed) return;
231
+ const keep = new Set(specs.map((s) => s.draftHandle.url));
232
+ for (const [key, task] of [...tasks]) {
233
+ if (!keep.has(key)) removeTask(task);
234
+ }
235
+ for (const spec of specs) {
236
+ const key = spec.draftHandle.url;
237
+ let task = tasks.get(key);
238
+ const membersChanged = !task || !sameMemberSets(task.spec.members, spec.members);
239
+ if (!task) {
240
+ task = { key, spec, listeners: /* @__PURE__ */ new Map(), queued: false, debounce: null };
241
+ tasks.set(key, task);
242
+ } else {
243
+ task.spec = spec;
244
+ }
245
+ void ensureListeners(task);
246
+ if (membersChanged) schedule(key);
247
+ }
248
+ }
249
+ function dispose() {
250
+ disposed = true;
251
+ for (const [, task] of [...tasks]) removeTask(task);
252
+ queue.length = 0;
253
+ }
254
+ return { setTimelines, dispose };
255
+ function removeTask(task) {
256
+ if (task.debounce) clearTimeout(task.debounce);
257
+ task.debounce = null;
258
+ for (const [, { handle, onChange }] of task.listeners) {
259
+ handle?.off("change", onChange);
260
+ }
261
+ task.listeners.clear();
262
+ tasks.delete(task.key);
263
+ }
264
+ function sameMemberSets(a, b) {
265
+ if (a.length !== b.length) return false;
266
+ const key = (m) => `${m.url}:${m.cloneUrl ?? ""}`;
267
+ const set = new Set(a.map(key));
268
+ return b.every((m) => set.has(key(m)));
269
+ }
270
+ async function ensureListeners(task) {
271
+ const wanted = new Set(
272
+ task.spec.members.map((m) => m.cloneUrl ?? m.url)
273
+ );
274
+ for (const [url, { handle, onChange }] of [...task.listeners]) {
275
+ if (!wanted.has(url)) {
276
+ handle?.off("change", onChange);
277
+ task.listeners.delete(url);
278
+ }
279
+ }
280
+ for (const url of wanted) {
281
+ if (task.listeners.has(url)) continue;
282
+ const onChange = (payload) => {
283
+ if (options.onLocalChange && LOCAL_PATCH_SOURCES.has(payload.patchInfo.source) && payload.doc) {
284
+ options.onLocalChange(payload.doc);
285
+ }
286
+ if (task.debounce) clearTimeout(task.debounce);
287
+ task.debounce = setTimeout(() => {
288
+ task.debounce = null;
289
+ schedule(task.key);
290
+ }, GROUPING_DEBOUNCE_MS);
291
+ };
292
+ const slot = { handle: null, onChange };
293
+ task.listeners.set(url, slot);
294
+ try {
295
+ const handle = await repo.find(url);
296
+ if (disposed || tasks.get(task.key) !== task) return;
297
+ if (task.listeners.get(url) !== slot) return;
298
+ handle.on("change", onChange);
299
+ slot.handle = handle;
300
+ } catch (err) {
301
+ if (task.listeners.get(url) === slot) task.listeners.delete(url);
302
+ console.warn(
303
+ "[drafts] change grouping: failed to watch member:",
304
+ url,
305
+ err
306
+ );
307
+ }
308
+ }
309
+ }
310
+ function schedule(key) {
311
+ if (disposed) return;
312
+ const task = tasks.get(key);
313
+ if (!task || task.queued) return;
314
+ task.queued = true;
315
+ queue.push(key);
316
+ void pump();
317
+ }
318
+ async function pump() {
319
+ if (running || disposed) return;
320
+ running = true;
321
+ try {
322
+ while (queue.length > 0 && !disposed) {
323
+ const key = queue.shift();
324
+ const task = tasks.get(key);
325
+ if (!task) continue;
326
+ task.queued = false;
327
+ try {
328
+ await updateTimelineGroups(task);
329
+ } catch (err) {
330
+ console.warn(
331
+ "[drafts] change grouping failed for:",
332
+ key,
333
+ err
334
+ );
335
+ }
336
+ }
337
+ } finally {
338
+ running = false;
339
+ }
340
+ }
341
+ async function updateTimelineGroups(task) {
342
+ const spec = task.spec;
343
+ const isAborted = () => disposed || tasks.get(task.key) !== task;
344
+ if (!spec.draftHandle.doc()) return;
345
+ const changeGroupHandle = await ensureChangeGroupDoc(
346
+ repo,
347
+ spec.draftHandle
348
+ );
349
+ const createdAt = await creationTime(spec.rootDocUrl);
350
+ if (isAborted()) return;
351
+ const members = [...spec.members].sort(byMemberUrl);
352
+ const sources = [];
353
+ for (const member of members) {
354
+ try {
355
+ const handle = await repo.find(member.cloneUrl ?? member.url);
356
+ const doc = handle.doc();
357
+ if (doc) sources.push({ member, doc });
358
+ } catch (err) {
359
+ console.warn(
360
+ "[drafts] change grouping: failed to resolve member:",
361
+ member,
362
+ err
363
+ );
364
+ }
365
+ }
366
+ if (isAborted()) return;
367
+ const changeGroupDoc = changeGroupHandle.doc();
368
+ if (!changeGroupDoc) return;
369
+ const computedThrough = changeGroupDoc.computedThrough ?? {};
370
+ const frontier = {};
371
+ const tails = [];
372
+ for (const { member, doc } of sources) {
373
+ frontier[member.url] = encodeHeads(Automerge.getHeads(doc));
374
+ const consumed = computedThrough[member.url];
375
+ const since = consumed ? decodeHeads(consumed) : member.clonedAt ? decodeHeads(member.clonedAt) : [];
376
+ collectMemberRows(tails, member, doc, since, createdAt);
377
+ }
378
+ if (tails.length === 0) {
379
+ const stale = Object.entries(frontier).filter(
380
+ ([url, heads]) => !sameHeads(computedThrough[url], heads)
381
+ );
382
+ if (stale.length > 0) {
383
+ changeGroupHandle.change((d) => {
384
+ for (const [url, heads] of stale) {
385
+ d.computedThrough[url] = heads;
386
+ }
387
+ });
388
+ }
389
+ return;
390
+ }
391
+ tails.sort(newestFirst);
392
+ const stored = Object.values(changeGroupDoc.groups ?? {}).sort(
393
+ (a, b) => b.endTime - a.endTime
394
+ );
395
+ const newestStored = stored[0];
396
+ const secondStored = stored[1];
397
+ const tailOldestMs = tails[tails.length - 1].time * 1e3;
398
+ const fastOk = !!newestStored && tailOldestMs >= newestStored.startTime * 1e3 - INACTIVITY_GAP_MS && (!secondStored || tailOldestMs - secondStored.endTime * 1e3 > INACTIVITY_GAP_MS);
399
+ if (fastOk) {
400
+ await appendTail(
401
+ changeGroupHandle,
402
+ newestStored,
403
+ tails,
404
+ frontier,
405
+ isAborted
406
+ );
407
+ } else {
408
+ await rebuildAll(
409
+ changeGroupHandle,
410
+ sources,
411
+ createdAt,
412
+ frontier,
413
+ isAborted
414
+ );
415
+ }
416
+ }
417
+ async function appendTail(changeGroupHandle, newestStored, tailsNewestFirst, frontier, isAborted) {
418
+ const tailGroups = splitIntoGroups(tailsNewestFirst);
419
+ const oldestGroup = tailGroups[tailGroups.length - 1];
420
+ const oldestGroupOldestMs = oldestGroup[oldestGroup.length - 1].time * 1e3;
421
+ const attaches = oldestGroupOldestMs <= newestStored.endTime * 1e3 + INACTIVITY_GAP_MS;
422
+ const freshGroups = attaches ? tailGroups.slice(0, -1) : tailGroups;
423
+ const slicer = createSlicer(isAborted, () => {
424
+ });
425
+ const built = [];
426
+ for (const rows of freshGroups) {
427
+ const group = await buildGroup(rows, slicer);
428
+ if (group === null) return;
429
+ built.push(group);
430
+ }
431
+ let extensionSums = null;
432
+ if (attaches) {
433
+ let additions = 0;
434
+ let deletions = 0;
435
+ for (const row of oldestGroup) {
436
+ if (!await slicer.tick()) return;
437
+ const counts = computeEditCounts(row.doc, row.hash, row.deps);
438
+ additions += counts.additions;
439
+ deletions += counts.deletions;
440
+ }
441
+ extensionSums = { additions, deletions };
442
+ }
443
+ if (isAborted()) return;
444
+ changeGroupHandle.change((d) => {
445
+ for (const group of built) d.groups[group.id] = group;
446
+ if (attaches && extensionSums) {
447
+ extendGroup(d, newestStored, oldestGroup, extensionSums);
448
+ }
449
+ for (const [url, heads] of Object.entries(frontier)) {
450
+ d.computedThrough[url] = heads;
451
+ }
452
+ });
453
+ }
454
+ function extendGroup(d, storedSnapshot, tailNewestFirst, sums) {
455
+ const tailNewest = tailNewestFirst[0];
456
+ const tailOldest = tailNewestFirst[tailNewestFirst.length - 1];
457
+ const base = d.groups[storedSnapshot.id];
458
+ if (!base) {
459
+ d.groups[`tg-${tailNewest.hash}`] = {
460
+ id: `tg-${tailNewest.hash}`,
461
+ startTime: tailOldest.time,
462
+ endTime: tailNewest.time,
463
+ newestMemberUrl: tailNewest.memberUrl,
464
+ newestHash: tailNewest.hash,
465
+ actors: dedupedActors(tailNewestFirst),
466
+ additions: sums.additions,
467
+ deletions: sums.deletions,
468
+ changeCount: tailNewestFirst.length
469
+ };
470
+ return;
471
+ }
472
+ const newer = tailNewest.time > base.endTime;
473
+ const tailActors = dedupedActors(tailNewestFirst);
474
+ const baseActors = [...base.actors];
475
+ const actors = newer ? [...tailActors, ...baseActors.filter((a) => !tailActors.includes(a))] : [...baseActors, ...tailActors.filter((a) => !baseActors.includes(a))];
476
+ const extended = {
477
+ id: newer ? `tg-${tailNewest.hash}` : base.id,
478
+ startTime: Math.min(base.startTime, tailOldest.time),
479
+ endTime: Math.max(base.endTime, tailNewest.time),
480
+ newestMemberUrl: newer ? tailNewest.memberUrl : base.newestMemberUrl,
481
+ newestHash: newer ? tailNewest.hash : base.newestHash,
482
+ actors,
483
+ additions: base.additions + sums.additions,
484
+ deletions: base.deletions + sums.deletions,
485
+ changeCount: base.changeCount + tailNewestFirst.length
486
+ };
487
+ if (extended.id !== storedSnapshot.id) delete d.groups[storedSnapshot.id];
488
+ d.groups[extended.id] = extended;
489
+ }
490
+ async function rebuildAll(changeGroupHandle, sources, createdAt, frontier, isAborted) {
491
+ const rows = [];
492
+ for (const { member, doc } of sources) {
493
+ const since = member.clonedAt ? decodeHeads(member.clonedAt) : [];
494
+ collectMemberRows(rows, member, doc, since, createdAt);
495
+ }
496
+ rows.sort(newestFirst);
497
+ const groupsRows = splitIntoGroups(rows);
498
+ const expectedIds = new Set(groupsRows.map(groupId));
499
+ const batch = [];
500
+ const flush = () => {
501
+ if (batch.length === 0) return;
502
+ changeGroupHandle.change((d) => {
503
+ for (const group of batch) d.groups[group.id] = group;
504
+ });
505
+ batch.length = 0;
506
+ };
507
+ const slicer = createSlicer(isAborted, flush);
508
+ for (const groupRows of groupsRows) {
509
+ const id = groupId(groupRows);
510
+ const existing = changeGroupHandle.doc()?.groups?.[id];
511
+ if (existing && existing.changeCount === groupRows.length && existing.startTime === groupRows[groupRows.length - 1].time && existing.endTime === groupRows[0].time) {
512
+ continue;
513
+ }
514
+ const group = await buildGroup(groupRows, slicer);
515
+ if (group === null) return;
516
+ batch.push(group);
517
+ }
518
+ if (isAborted()) return;
519
+ changeGroupHandle.change((d) => {
520
+ for (const group of batch) d.groups[group.id] = group;
521
+ for (const id of Object.keys(d.groups)) {
522
+ if (!expectedIds.has(id)) delete d.groups[id];
523
+ }
524
+ for (const [url, heads] of Object.entries(frontier)) {
525
+ d.computedThrough[url] = heads;
526
+ }
527
+ });
528
+ }
529
+ }
530
+ async function ensureMainDraft(repo, docHandle) {
531
+ const existingUrl = docHandle.doc()?.["@patchwork"]?.mainDraftUrl;
532
+ if (existingUrl) return repo.find(existingUrl);
533
+ const mainDraft = repo.create({
534
+ "@patchwork": { type: "draft" },
535
+ isMain: true,
536
+ parent: docHandle.url,
537
+ drafts: [],
538
+ clones: {}
539
+ });
540
+ docHandle.change((d) => {
541
+ if (!d["@patchwork"]) d["@patchwork"] = {};
542
+ d["@patchwork"].mainDraftUrl = mainDraft.url;
543
+ });
544
+ return mainDraft;
545
+ }
546
+ export {
547
+ computeRangeEditCounts as a,
548
+ computeEditCounts as b,
549
+ createChangeGrouper as c,
550
+ ensureMainDraft as e,
551
+ getDocCreationTime as g,
552
+ sameHeads as s
553
+ };
554
+ //# sourceMappingURL=draft-docs-Bi8K1y3a.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-docs-Bi8K1y3a.js","sources":["../../src/change-group-cache.ts","../../src/draft-docs.ts"],"sourcesContent":["import {\n decodeHeads,\n encodeHeads,\n isValidAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type DocHandleChangePayload,\n type Repo,\n type UrlHeads,\n} from \"@automerge/automerge-repo/slim\";\nimport * as Automerge from \"@automerge/automerge/slim\";\n\nimport type {\n ChangeGroup,\n ChangeGroupDoc,\n DraftDoc,\n DraftMemberDoc,\n} from \"./draft-types.js\";\n\n// Bump to discard every existing group doc's contents (they self-rebuild).\nexport const CHANGE_GROUP_DOC_VERSION = 1;\n\n// A pause between consecutive changes longer than this starts a new group:\n// bursts of continuous editing read as a single row, however long they run,\n// and any ten-minute-plus lull splits the timeline. Baked into the group\n// doc — changing it invalidates and rebuilds every document.\nexport const INACTIVITY_GAP_MS = 10 * 60 * 1000;\n\n// How long grouping may hold the main thread before yielding to idle time.\nconst SLICE_BUDGET_MS = 8;\n\n// Coalesce bursts of member-doc change events into one grouping update.\nconst GROUPING_DEBOUNCE_MS = 250;\n\n// Change-event sources that mean a LOCAL edit (made through this client's\n// doc instance), as opposed to synced/merged remote changes.\nconst LOCAL_PATCH_SOURCES = new Set([\"change\", \"changeAt\", \"emptyChange\"]);\n\n// One timeline the ChangeGrouper is responsible for: the DraftDoc that owns it\n// (main included — the main draft is always real), the member docs whose\n// interleaved changes make it up, and the host doc whose creation time is\n// the \"before this document existed\" cutoff.\nexport type TimelineGroupingSpec = {\n draftHandle: DocHandle<DraftDoc>;\n members: DraftMemberDoc[];\n rootDocUrl: AutomergeUrl;\n};\n\nexport type ChangeGrouper = {\n // Reconcile the timelines to keep grouped (ordered by priority). Attaches\n // member-doc change listeners that drive incremental grouping updates;\n // timelines absent from the list are torn down.\n setTimelines: (specs: TimelineGroupingSpec[]) => void;\n dispose: () => void;\n};\n\nexport type ChangeGrouperOptions = {\n // Called when a watched member doc receives a local change — i.e. the\n // current user just wrote with that doc instance's actor id. Feeds the\n // ActorRecorder (see actor-attribution.ts).\n onLocalChange?: (doc: Automerge.Doc<unknown>) => void;\n};\n\n// Resolve a draft's change-group doc, creating it and stamping\n// `changeGroupDocUrl` the first time. A document whose format version or\n// grouping parameter no longer matches is emptied in place and rebuilt.\nexport async function ensureChangeGroupDoc(\n repo: Repo,\n draftHandle: DocHandle<DraftDoc>\n): Promise<DocHandle<ChangeGroupDoc>> {\n const draft = draftHandle.doc();\n const existingUrl = getChangeGroupDocUrl(draft);\n if (existingUrl && isValidAutomergeUrl(existingUrl)) {\n const handle = await repo.find<ChangeGroupDoc>(existingUrl);\n const doc = handle.doc();\n if (\n doc &&\n (doc.version !== CHANGE_GROUP_DOC_VERSION ||\n doc.inactivityGapMs !== INACTIVITY_GAP_MS)\n ) {\n handle.change((d) => {\n d.version = CHANGE_GROUP_DOC_VERSION;\n d.inactivityGapMs = INACTIVITY_GAP_MS;\n d.groups = {};\n d.computedThrough = {};\n });\n }\n if (!draft?.changeGroupDocUrl) {\n draftHandle.change((d) => {\n if (!d.changeGroupDocUrl) d.changeGroupDocUrl = existingUrl;\n });\n }\n return handle;\n }\n\n const changeGroupHandle = repo.create<ChangeGroupDoc>({\n \"@patchwork\": { type: \"change-group\" },\n version: CHANGE_GROUP_DOC_VERSION,\n inactivityGapMs: INACTIVITY_GAP_MS,\n groups: {},\n computedThrough: {},\n });\n draftHandle.change((d) => {\n if (!d.changeGroupDocUrl) d.changeGroupDocUrl = changeGroupHandle.url;\n });\n // A concurrent creator may have won the stamp; honor whichever pointer\n // settled (our fresh doc is then an accepted orphan, same as mainDraftUrl).\n const settled = draftHandle.doc()?.changeGroupDocUrl;\n if (\n settled &&\n settled !== changeGroupHandle.url &&\n isValidAutomergeUrl(settled)\n ) {\n return repo.find<ChangeGroupDoc>(settled);\n }\n return changeGroupHandle;\n}\n\nfunction getChangeGroupDocUrl(\n draft: DraftDoc | undefined\n): AutomergeUrl | undefined {\n const legacyDraft = draft as\n | (DraftDoc & { changeGroupCacheUrl?: AutomergeUrl })\n | undefined;\n return draft?.changeGroupDocUrl ?? legacyDraft?.changeGroupCacheUrl;\n}\n\n// Work out one change's rough edit magnitude by diffing it against its parents\n// and counting its patches: splice lengths and insert counts as additions, del\n// lengths as deletions, everything else (put / inc / mark / …) as one addition.\n// `@patchwork` metadata paths are ignored. A pure function of immutable\n// history, so each change is diffed once, here, and only the group aggregates\n// are kept.\nexport function computeEditCounts(\n doc: Automerge.Doc<unknown>,\n hash: string,\n deps: string[]\n): { additions: number; deletions: number } {\n try {\n return countPatches(\n Automerge.diff(\n doc,\n deps as unknown as Automerge.Heads,\n [hash] as unknown as Automerge.Heads\n )\n );\n } catch (err) {\n console.warn(\"[drafts] failed to diff change for edit counts:\", hash, err);\n return { additions: 0, deletions: 0 };\n }\n}\n\n// The same +/- magnitude over an arbitrary head range (`from` → `to`), e.g.\n// the eye's diff span between the baseline and the scrubbed head.\nexport function computeRangeEditCounts(\n doc: Automerge.Doc<unknown>,\n from: string[],\n to: string[]\n): { additions: number; deletions: number } {\n try {\n return countPatches(\n Automerge.diff(\n doc,\n from as unknown as Automerge.Heads,\n to as unknown as Automerge.Heads\n )\n );\n } catch (err) {\n console.warn(\"[drafts] failed to diff range for edit counts:\", err);\n return { additions: 0, deletions: 0 };\n }\n}\n\n// Shared patch-counting rules for the two diff flavors above.\nfunction countPatches(patches: Automerge.Patch[]): {\n additions: number;\n deletions: number;\n} {\n let additions = 0;\n let deletions = 0;\n for (const patch of patches) {\n if (patch.path[0] === \"@patchwork\") continue;\n if (patch.action === \"splice\") {\n additions += (patch.value as string).length;\n } else if (patch.action === \"insert\") {\n additions += Array.isArray((patch as { values?: unknown[] }).values)\n ? (patch as { values: unknown[] }).values.length\n : 1;\n } else if (patch.action === \"del\") {\n deletions += (patch as { length?: number }).length ?? 1;\n } else {\n additions += 1;\n }\n }\n return { additions, deletions };\n}\n\n// When was a document created, as a Unix SECONDS timestamp? Reads the doc's\n// full history and returns its first change's time (the creation change).\n// Returns undefined when the doc, its history, or that time can't be resolved,\n// in which case callers skip the \"before creation\" filter rather than hiding\n// everything.\nexport async function getDocCreationTime(\n repo: Repo,\n url: AutomergeUrl | undefined\n): Promise<number | undefined> {\n if (!url) return undefined;\n try {\n const handle = await repo.find<unknown>(url);\n const doc = handle.doc();\n if (!doc) return undefined;\n const metas = Automerge.getChangesMetaSince(doc, []);\n return metas[0]?.time || undefined;\n } catch (err) {\n console.warn(\"[drafts] failed to resolve creation time for:\", url, err);\n return undefined;\n }\n}\n\n// One member change awaiting aggregation. `seq` is the change's index within\n// its member's gathered metas, used only to break same-second timestamp ties\n// (meta.time is second-resolution) with the doc's own causal order.\ntype PendingChange = {\n memberUrl: AutomergeUrl;\n doc: Automerge.Doc<unknown>;\n hash: string;\n deps: string[];\n time: number;\n actor: string;\n seq: number;\n};\n\n// Newest first by timestamp, per-doc causal order breaking same-second ties.\n// The sidebar's on-demand scrub resolution MUST order identically, or the\n// scrubber's index math drifts from `changeCount`.\nfunction newestFirst(a: PendingChange, b: PendingChange): number {\n return b.time - a.time || b.seq - a.seq;\n}\n\n// Fold a flat, newest-first list of changes into groups: consecutive changes\n// stay together while the pause between them is at most the inactivity gap.\nfunction splitIntoGroups(\n rowsNewestFirst: PendingChange[]\n): PendingChange[][] {\n const groups: PendingChange[][] = [];\n let window: PendingChange[] = [];\n let prevTimeMs: number | null = null;\n for (const row of rowsNewestFirst) {\n const timeMs = row.time * 1000;\n // Rows arrive newest-first, so the previous row is this change's newer\n // neighbour; a gap larger than the threshold between them is a lull.\n if (\n prevTimeMs !== null &&\n prevTimeMs - timeMs > INACTIVITY_GAP_MS &&\n window.length > 0\n ) {\n groups.push(window);\n window = [];\n }\n window.push(row);\n prevTimeMs = timeMs;\n }\n if (window.length > 0) groups.push(window);\n return groups;\n}\n\nfunction groupId(rowsNewestFirst: PendingChange[]): string {\n return `tg-${rowsNewestFirst[0].hash}`;\n}\n\n// Append `member`'s changes since `since` onto `out`, dropping anything from\n// before the root document was created (a member dragged in after the fact\n// would otherwise contribute pre-existing history that reads as noise). `seq`\n// still reflects each change's position in the gathered metas, so filtering\n// doesn't disturb the tie-break ordering.\nfunction collectMemberRows(\n out: PendingChange[],\n member: DraftMemberDoc,\n doc: Automerge.Doc<unknown>,\n since: Automerge.Heads,\n createdAt: number | undefined\n): void {\n let metas;\n try {\n metas = Automerge.getChangesMetaSince(doc, since);\n } catch (err) {\n console.warn(\n \"[drafts] change grouping: failed to read changes for member:\",\n member.url,\n err\n );\n return;\n }\n metas.forEach((meta, seq) => {\n if (createdAt !== undefined && meta.time && meta.time < createdAt) return;\n out.push({\n memberUrl: member.url,\n doc,\n hash: meta.hash,\n deps: meta.deps,\n time: meta.time,\n actor: meta.actor,\n seq,\n });\n });\n}\n\nfunction dedupedActors(rowsNewestFirst: PendingChange[]): string[] {\n const actors: string[] = [];\n for (const row of rowsNewestFirst) {\n if (!actors.includes(row.actor)) actors.push(row.actor);\n }\n return actors;\n}\n\n// Yield to the main thread between diff slices.\nfunction idle(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestIdleCallback === \"function\") {\n requestIdleCallback(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\n// Time-budgeted cooperative slicing: `tick()` resolves immediately while the\n// current slice has budget left, otherwise runs `onYield` (flush pending\n// writes) and waits for idle time. Resolves false once the run is aborted.\ntype Slicer = { tick: () => Promise<boolean> };\n\nfunction createSlicer(isAborted: () => boolean, onYield: () => void): Slicer {\n let sliceStart = performance.now();\n return {\n async tick(): Promise<boolean> {\n if (isAborted()) return false;\n if (performance.now() - sliceStart < SLICE_BUDGET_MS) return true;\n onYield();\n await idle();\n sliceStart = performance.now();\n return !isAborted();\n },\n };\n}\n\n// Diff every change in a group (newest-first, sliced) and aggregate the\n// result down to the ChangeGroup a timeline row renders. Returns null when\n// the run was aborted mid-diff.\nasync function buildGroup(\n rowsNewestFirst: PendingChange[],\n slicer: Slicer\n): Promise<ChangeGroup | null> {\n let additions = 0;\n let deletions = 0;\n for (const row of rowsNewestFirst) {\n if (!(await slicer.tick())) return null;\n const counts = computeEditCounts(row.doc, row.hash, row.deps);\n additions += counts.additions;\n deletions += counts.deletions;\n }\n const newest = rowsNewestFirst[0];\n const oldest = rowsNewestFirst[rowsNewestFirst.length - 1];\n return {\n id: groupId(rowsNewestFirst),\n startTime: oldest.time,\n endTime: newest.time,\n newestMemberUrl: newest.memberUrl,\n newestHash: newest.hash,\n actors: dedupedActors(rowsNewestFirst),\n additions,\n deletions,\n changeCount: rowsNewestFirst.length,\n };\n}\n\nfunction byMemberUrl(a: DraftMemberDoc, b: DraftMemberDoc): number {\n return a.url < b.url ? -1 : a.url > b.url ? 1 : 0;\n}\n\nexport function sameHeads(\n a: UrlHeads | undefined,\n b: UrlHeads | undefined\n): boolean {\n if (a === b) return true;\n if (!a || !b || a.length !== b.length) return false;\n const set = new Set(b);\n return a.every((h) => set.has(h));\n}\n\n// Groups each timeline's changes into its ChangeGroupDoc. It owns one\n// background task per timeline, listens to member docs for edits, and updates\n// newest groups first while older history backfills. One global runner\n// processes timelines sequentially in the priority order it was given.\nexport function createChangeGrouper(\n repo: Repo,\n options: ChangeGrouperOptions = {}\n): ChangeGrouper {\n // Change listeners on the docs a timeline reads (originals for main,\n // clones for drafts). `handle` is null while the doc is still resolving —\n // the slot is reserved up front so concurrent syncs don't double-attach.\n type SourceListener = {\n handle: DocHandle<unknown> | null;\n onChange: (payload: DocHandleChangePayload<unknown>) => void;\n };\n\n type Task = {\n key: AutomergeUrl; // the DraftDoc url\n spec: TimelineGroupingSpec;\n listeners: Map<AutomergeUrl, SourceListener>;\n queued: boolean;\n debounce: ReturnType<typeof setTimeout> | null;\n };\n\n const tasks = new Map<AutomergeUrl, Task>();\n const queue: AutomergeUrl[] = [];\n let running = false;\n let disposed = false;\n\n // Host-doc creation times, resolved once per root url.\n const creationTimes = new Map<AutomergeUrl, Promise<number | undefined>>();\n const creationTime = (url: AutomergeUrl): Promise<number | undefined> => {\n let cached = creationTimes.get(url);\n if (!cached) {\n cached = getDocCreationTime(repo, url);\n creationTimes.set(url, cached);\n }\n return cached;\n };\n\n function setTimelines(specs: TimelineGroupingSpec[]): void {\n if (disposed) return;\n const keep = new Set(specs.map((s) => s.draftHandle.url));\n for (const [key, task] of [...tasks]) {\n if (!keep.has(key)) removeTask(task);\n }\n for (const spec of specs) {\n const key = spec.draftHandle.url;\n let task = tasks.get(key);\n const membersChanged =\n !task || !sameMemberSets(task.spec.members, spec.members);\n if (!task) {\n task = { key, spec, listeners: new Map(), queued: false, debounce: null };\n tasks.set(key, task);\n } else {\n task.spec = spec;\n }\n void ensureListeners(task);\n if (membersChanged) schedule(key);\n }\n }\n\n function dispose(): void {\n disposed = true;\n for (const [, task] of [...tasks]) removeTask(task);\n queue.length = 0;\n }\n\n return { setTimelines, dispose };\n\n function removeTask(task: Task): void {\n if (task.debounce) clearTimeout(task.debounce);\n task.debounce = null;\n for (const [, { handle, onChange }] of task.listeners) {\n handle?.off(\"change\", onChange);\n }\n task.listeners.clear();\n // A queued entry is skipped by the pump once the task is gone.\n tasks.delete(task.key);\n }\n\n function sameMemberSets(a: DraftMemberDoc[], b: DraftMemberDoc[]): boolean {\n if (a.length !== b.length) return false;\n const key = (m: DraftMemberDoc) => `${m.url}:${m.cloneUrl ?? \"\"}`;\n const set = new Set(a.map(key));\n return b.every((m) => set.has(key(m)));\n }\n\n // Keep exactly one change listener per member source doc; edits schedule a\n // debounced grouping update for the owning timeline.\n async function ensureListeners(task: Task): Promise<void> {\n const wanted = new Set(\n task.spec.members.map((m) => m.cloneUrl ?? m.url)\n );\n for (const [url, { handle, onChange }] of [...task.listeners]) {\n if (!wanted.has(url)) {\n handle?.off(\"change\", onChange);\n task.listeners.delete(url);\n }\n }\n for (const url of wanted) {\n if (task.listeners.has(url)) continue;\n const onChange = (payload: DocHandleChangePayload<unknown>) => {\n if (\n options.onLocalChange &&\n LOCAL_PATCH_SOURCES.has(payload.patchInfo.source) &&\n payload.doc\n ) {\n options.onLocalChange(payload.doc);\n }\n if (task.debounce) clearTimeout(task.debounce);\n task.debounce = setTimeout(() => {\n task.debounce = null;\n schedule(task.key);\n }, GROUPING_DEBOUNCE_MS);\n };\n const slot: SourceListener = { handle: null, onChange };\n task.listeners.set(url, slot);\n try {\n const handle = await repo.find<unknown>(url);\n // The task was torn down or the slot dropped while resolving.\n if (disposed || tasks.get(task.key) !== task) return;\n if (task.listeners.get(url) !== slot) return;\n handle.on(\"change\", onChange);\n slot.handle = handle;\n } catch (err) {\n if (task.listeners.get(url) === slot) task.listeners.delete(url);\n console.warn(\n \"[drafts] change grouping: failed to watch member:\",\n url,\n err\n );\n }\n }\n }\n\n function schedule(key: AutomergeUrl): void {\n if (disposed) return;\n const task = tasks.get(key);\n if (!task || task.queued) return;\n task.queued = true;\n queue.push(key);\n void pump();\n }\n\n async function pump(): Promise<void> {\n if (running || disposed) return;\n running = true;\n try {\n while (queue.length > 0 && !disposed) {\n const key = queue.shift()!;\n const task = tasks.get(key);\n if (!task) continue;\n task.queued = false;\n try {\n await updateTimelineGroups(task);\n } catch (err) {\n console.warn(\n \"[drafts] change grouping failed for:\",\n key,\n err\n );\n }\n }\n } finally {\n running = false;\n }\n }\n\n // Update one timeline. The common case appends the unconsumed\n // tail onto the newest group; anything that would reshape older groups\n // (late-syncing changes, a fresh document) falls back to a full rebuild\n // that reuses stored groups wherever they come out identical.\n async function updateTimelineGroups(task: Task): Promise<void> {\n const spec = task.spec;\n // The run is stale once the task was replaced or torn down (a newer spec\n // re-queues itself), or the ChangeGrouper was disposed.\n const isAborted = () => disposed || tasks.get(task.key) !== task;\n\n if (!spec.draftHandle.doc()) return;\n const changeGroupHandle = await ensureChangeGroupDoc(\n repo,\n spec.draftHandle\n );\n const createdAt = await creationTime(spec.rootDocUrl);\n if (isAborted()) return;\n\n // Resolve member sources, sorted by member url so cross-doc timestamp\n // ties interleave identically on every client.\n const members = [...spec.members].sort(byMemberUrl);\n const sources: { member: DraftMemberDoc; doc: Automerge.Doc<unknown> }[] =\n [];\n for (const member of members) {\n try {\n const handle = await repo.find<unknown>(member.cloneUrl ?? member.url);\n const doc = handle.doc();\n if (doc) sources.push({ member, doc: doc as Automerge.Doc<unknown> });\n } catch (err) {\n console.warn(\n \"[drafts] change grouping: failed to resolve member:\",\n member,\n err\n );\n }\n }\n if (isAborted()) return;\n\n const changeGroupDoc = changeGroupHandle.doc();\n if (!changeGroupDoc) return;\n const computedThrough = changeGroupDoc.computedThrough ?? {};\n\n // Each member's frontier as of this gather; the consumed marker advances\n // to exactly these once the run completes, so the next run's\n // getChangesMetaSince yields precisely the unconsumed tail.\n const frontier: Record<AutomergeUrl, UrlHeads> = {};\n const tails: PendingChange[] = [];\n for (const { member, doc } of sources) {\n frontier[member.url] = encodeHeads(Automerge.getHeads(doc));\n const consumed = computedThrough[member.url];\n const since = consumed\n ? decodeHeads(consumed)\n : member.clonedAt\n ? decodeHeads(member.clonedAt)\n : [];\n collectMemberRows(tails, member, doc, since, createdAt);\n }\n\n if (tails.length === 0) {\n // Nothing new to group; just record any frontier movement (e.g. members\n // whose unconsumed changes were all filtered out, or brand-new members\n // with no post-fork changes yet).\n const stale = Object.entries(frontier).filter(\n ([url, heads]) => !sameHeads(computedThrough[url as AutomergeUrl], heads)\n );\n if (stale.length > 0) {\n changeGroupHandle.change((d) => {\n for (const [url, heads] of stale) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n return;\n }\n\n tails.sort(newestFirst);\n\n // Fast path: every new change lands on or after the newest stored group\n // (extending it or opening newer ones) without bridging into the group\n // below it — the overwhelmingly common live-editing case. Everything else\n // (first build, members without a consumed marker, late-syncing changes\n // with old timestamps) rebuilds via the full pass.\n const stored = Object.values(changeGroupDoc.groups ?? {}).sort(\n (a, b) => b.endTime - a.endTime\n );\n const newestStored = stored[0];\n const secondStored = stored[1];\n const tailOldestMs = tails[tails.length - 1].time * 1000;\n const fastOk =\n !!newestStored &&\n tailOldestMs >= newestStored.startTime * 1000 - INACTIVITY_GAP_MS &&\n (!secondStored ||\n tailOldestMs - secondStored.endTime * 1000 > INACTIVITY_GAP_MS);\n\n if (fastOk) {\n await appendTail(\n changeGroupHandle,\n newestStored,\n tails,\n frontier,\n isAborted\n );\n } else {\n await rebuildAll(\n changeGroupHandle,\n sources,\n createdAt,\n frontier,\n isAborted\n );\n }\n }\n\n // Incremental append: diff only the tail, then extend the newest stored\n // group (accumulate counts, union actors, bump the anchor) and/or open new\n // groups above it. No stored aggregate is ever decomposed.\n async function appendTail(\n changeGroupHandle: DocHandle<ChangeGroupDoc>,\n newestStored: ChangeGroup,\n tailsNewestFirst: PendingChange[],\n frontier: Record<AutomergeUrl, UrlHeads>,\n isAborted: () => boolean\n ): Promise<void> {\n const tailGroups = splitIntoGroups(tailsNewestFirst);\n const oldestGroup = tailGroups[tailGroups.length - 1];\n const oldestGroupOldestMs =\n oldestGroup[oldestGroup.length - 1].time * 1000;\n // The oldest run of new changes merges into the stored group when no lull\n // separates them (it may even start inside the stored span).\n const attaches =\n oldestGroupOldestMs <= newestStored.endTime * 1000 + INACTIVITY_GAP_MS;\n const freshGroups = attaches ? tailGroups.slice(0, -1) : tailGroups;\n\n const slicer = createSlicer(isAborted, () => {});\n const built: ChangeGroup[] = [];\n for (const rows of freshGroups) {\n const group = await buildGroup(rows, slicer);\n if (group === null) return;\n built.push(group);\n }\n\n let extensionSums: { additions: number; deletions: number } | null = null;\n if (attaches) {\n let additions = 0;\n let deletions = 0;\n for (const row of oldestGroup) {\n if (!(await slicer.tick())) return;\n const counts = computeEditCounts(row.doc, row.hash, row.deps);\n additions += counts.additions;\n deletions += counts.deletions;\n }\n extensionSums = { additions, deletions };\n }\n\n if (isAborted()) return;\n changeGroupHandle.change((d) => {\n for (const group of built) d.groups[group.id] = group;\n if (attaches && extensionSums) {\n extendGroup(d, newestStored, oldestGroup, extensionSums);\n }\n for (const [url, heads] of Object.entries(frontier)) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n\n // Fold a tail run into the newest stored group inside an open change():\n // re-read the group from the live doc so a concurrently synced extension is\n // extended further rather than clobbered.\n function extendGroup(\n d: ChangeGroupDoc,\n storedSnapshot: ChangeGroup,\n tailNewestFirst: PendingChange[],\n sums: { additions: number; deletions: number }\n ): void {\n const tailNewest = tailNewestFirst[0];\n const tailOldest = tailNewestFirst[tailNewestFirst.length - 1];\n const base = d.groups[storedSnapshot.id];\n if (!base) {\n // The stored group vanished under us (concurrent rewrite); keep the\n // tail as its own group rather than losing it — the next full pass\n // reconciles the shape.\n d.groups[`tg-${tailNewest.hash}`] = {\n id: `tg-${tailNewest.hash}`,\n startTime: tailOldest.time,\n endTime: tailNewest.time,\n newestMemberUrl: tailNewest.memberUrl,\n newestHash: tailNewest.hash,\n actors: dedupedActors(tailNewestFirst),\n additions: sums.additions,\n deletions: sums.deletions,\n changeCount: tailNewestFirst.length,\n };\n return;\n }\n\n // The tail is newer causally, but a merge can deliver changes stamped\n // inside the stored span; only a strictly later timestamp moves the\n // anchor.\n const newer = tailNewest.time > base.endTime;\n const tailActors = dedupedActors(tailNewestFirst);\n const baseActors = [...base.actors];\n const actors = newer\n ? [...tailActors, ...baseActors.filter((a) => !tailActors.includes(a))]\n : [...baseActors, ...tailActors.filter((a) => !baseActors.includes(a))];\n\n const extended: ChangeGroup = {\n id: newer ? `tg-${tailNewest.hash}` : base.id,\n startTime: Math.min(base.startTime, tailOldest.time),\n endTime: Math.max(base.endTime, tailNewest.time),\n newestMemberUrl: newer ? tailNewest.memberUrl : base.newestMemberUrl,\n newestHash: newer ? tailNewest.hash : base.newestHash,\n actors,\n additions: base.additions + sums.additions,\n deletions: base.deletions + sums.deletions,\n changeCount: base.changeCount + tailNewestFirst.length,\n };\n if (extended.id !== storedSnapshot.id) delete d.groups[storedSnapshot.id];\n d.groups[extended.id] = extended;\n }\n\n // Full rebuild: regather every member's post-fork history, re-split, and\n // diff newest-first in idle slices — flushing completed groups as each\n // slice ends so recent history paints while older history backfills. A\n // stored group whose id, span, and change count match is reused without\n // re-diffing (cheap warm restarts, and no redundant work when another\n // client's grouping update syncs in). Stale ids and consumed markers settle\n // in the final write.\n async function rebuildAll(\n changeGroupHandle: DocHandle<ChangeGroupDoc>,\n sources: { member: DraftMemberDoc; doc: Automerge.Doc<unknown> }[],\n createdAt: number | undefined,\n frontier: Record<AutomergeUrl, UrlHeads>,\n isAborted: () => boolean\n ): Promise<void> {\n const rows: PendingChange[] = [];\n for (const { member, doc } of sources) {\n const since = member.clonedAt ? decodeHeads(member.clonedAt) : [];\n collectMemberRows(rows, member, doc, since, createdAt);\n }\n rows.sort(newestFirst);\n const groupsRows = splitIntoGroups(rows);\n const expectedIds = new Set(groupsRows.map(groupId));\n\n const batch: ChangeGroup[] = [];\n const flush = () => {\n if (batch.length === 0) return;\n changeGroupHandle.change((d) => {\n for (const group of batch) d.groups[group.id] = group;\n });\n batch.length = 0;\n };\n\n const slicer = createSlicer(isAborted, flush);\n for (const groupRows of groupsRows) {\n const id = groupId(groupRows);\n const existing = changeGroupHandle.doc()?.groups?.[id];\n if (\n existing &&\n existing.changeCount === groupRows.length &&\n existing.startTime === groupRows[groupRows.length - 1].time &&\n existing.endTime === groupRows[0].time\n ) {\n continue;\n }\n const group = await buildGroup(groupRows, slicer);\n if (group === null) return; // aborted mid-diff; markers stay put\n batch.push(group);\n }\n\n if (isAborted()) return;\n changeGroupHandle.change((d) => {\n for (const group of batch) d.groups[group.id] = group;\n for (const id of Object.keys(d.groups)) {\n if (!expectedIds.has(id)) delete d.groups[id];\n }\n for (const [url, heads] of Object.entries(frontier)) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n}\n","import type {\n DocHandle,\n Repo,\n} from \"@automerge/automerge-repo/slim\";\n\nimport type { DraftDoc, HasDrafts } from \"./draft-types.js\";\n\n// Resolve the host doc's main draft, creating it and stamping\n// `@patchwork.mainDraftUrl` the first time.\nexport async function ensureMainDraft(\n repo: Repo,\n docHandle: DocHandle<HasDrafts>\n): Promise<DocHandle<DraftDoc>> {\n const existingUrl = docHandle.doc()?.[\"@patchwork\"]?.mainDraftUrl;\n if (existingUrl) return repo.find<DraftDoc>(existingUrl);\n\n const mainDraft = repo.create<DraftDoc>({\n \"@patchwork\": { type: \"draft\" },\n isMain: true,\n parent: docHandle.url,\n drafts: [],\n clones: {},\n });\n docHandle.change((d) => {\n // Mutate `@patchwork` in place. Reassigning a spread would carry\n // references to existing Automerge objects into a new object.\n if (!d[\"@patchwork\"]) d[\"@patchwork\"] = {};\n d[\"@patchwork\"]!.mainDraftUrl = mainDraft.url;\n });\n return mainDraft;\n}\n"],"names":[],"mappings":"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"}