@inkandswitch/patchwork-pkg-base 0.0.65 → 0.0.67

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 (60) hide show
  1. package/module-settings-url +1 -0
  2. package/package.json +2 -1
  3. package/packages/codemirror-markdown/example.js +1 -2
  4. package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js → DraftOverlayProvider-D2vIz7hy.js} +2 -3
  5. package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js.map → DraftOverlayProvider-D2vIz7hy.js.map} +1 -1
  6. package/packages/drafts/dist/assets/{DraftStateProvider-DX8b_RgQ.js → DraftStateProvider-Bhz0S4c1.js} +212 -87
  7. package/packages/drafts/dist/assets/DraftStateProvider-Bhz0S4c1.js.map +1 -0
  8. package/packages/drafts/dist/assets/{draft-docs-Bi8K1y3a.js → draft-docs-DyJ2l14d.js} +108 -99
  9. package/packages/drafts/dist/assets/draft-docs-DyJ2l14d.js.map +1 -0
  10. package/packages/drafts/dist/assets/{main-EEciTwbZ.js → main-CG9HOCai.js} +14 -10
  11. package/packages/drafts/dist/assets/main-CG9HOCai.js.map +1 -0
  12. package/packages/drafts/dist/index.js +9 -9
  13. package/packages/drafts/package.json +5 -5
  14. package/packages/sideboard/dist/{chunk-KHS2XJKP.js → chunk-6POQCUBB.js} +3 -3
  15. package/packages/sideboard/dist/{chunk-TXFTRSLY.js → chunk-DS3QVJZT.js} +3 -3
  16. package/packages/sideboard/dist/chunk-DS3QVJZT.js.map +7 -0
  17. package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js → create-new-menu-VA4ELRJ6.js} +2 -2
  18. package/packages/sideboard/dist/document-list-panel-GHYN6JR5.js +2 -0
  19. package/packages/sideboard/dist/index.js +1 -1
  20. package/packages/sideboard/dist/sideboard/sideboard.js +1 -1
  21. package/packages/tldraw4/dist/{chunk-IKZWERSR.js → chunk-G3PMV62Z.js} +1 -3
  22. package/packages/tldraw4/dist/{chunk-7CJQMVDI.js → chunk-PMHAL5YM.js} +4 -4
  23. package/packages/tldraw4/dist/datatype.js +2 -2
  24. package/packages/tldraw4/dist/main.js +1 -1
  25. package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js → sanitizeSvg-NCGPTJ3E.js} +2 -2
  26. package/packages/tldraw4/dist/tool.js +450 -5675
  27. package/packages/tldraw4/dist/tool.js.map +4 -4
  28. package/packages/tldraw4/package.json +0 -1
  29. package/packages/tldraw5/dist/{chunk-GKLJTYCF.js → chunk-DO47QRII.js} +1 -3
  30. package/packages/tldraw5/dist/{chunk-4JN3PIR7.js → chunk-FTKY6DDS.js} +3 -3
  31. package/packages/tldraw5/dist/{chunk-JDHJ334K.js → chunk-SZI4VCOI.js} +4 -4
  32. package/packages/tldraw5/dist/datatype.js +3 -3
  33. package/packages/tldraw5/dist/main.js +1 -1
  34. package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js → sanitizeSvg-J7F3MQGT.js} +2 -2
  35. package/packages/tldraw5/dist/{script-VW3AAU5T.js → script-4RKE5JKT.js} +3 -3
  36. package/packages/tldraw5/dist/script-4RKE5JKT.js.map +7 -0
  37. package/packages/tldraw5/dist/{tldr-archive-WERHN3T4.js → tldr-archive-5X7EMHWZ.js} +3 -3
  38. package/packages/tldraw5/dist/tool.js +293 -1543
  39. package/packages/tldraw5/dist/tool.js.map +4 -4
  40. package/packages/tldraw5/package.json +0 -1
  41. package/plugin-registry-url +1 -0
  42. package/packages/drafts/dist/assets/DraftStateProvider-DX8b_RgQ.js.map +0 -1
  43. package/packages/drafts/dist/assets/draft-docs-Bi8K1y3a.js.map +0 -1
  44. package/packages/drafts/dist/assets/index-FKZtof6Z.js +0 -67
  45. package/packages/drafts/dist/assets/index-FKZtof6Z.js.map +0 -1
  46. package/packages/drafts/dist/assets/main-EEciTwbZ.js.map +0 -1
  47. package/packages/sideboard/dist/chunk-TXFTRSLY.js.map +0 -7
  48. package/packages/sideboard/dist/document-list-panel-WDIVYTZU.js +0 -2
  49. package/packages/tldraw5/dist/script-VW3AAU5T.js.map +0 -7
  50. /package/packages/sideboard/dist/{chunk-KHS2XJKP.js.map → chunk-6POQCUBB.js.map} +0 -0
  51. /package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js.map → create-new-menu-VA4ELRJ6.js.map} +0 -0
  52. /package/packages/sideboard/dist/{document-list-panel-WDIVYTZU.js.map → document-list-panel-GHYN6JR5.js.map} +0 -0
  53. /package/packages/tldraw4/dist/{chunk-IKZWERSR.js.map → chunk-G3PMV62Z.js.map} +0 -0
  54. /package/packages/tldraw4/dist/{chunk-7CJQMVDI.js.map → chunk-PMHAL5YM.js.map} +0 -0
  55. /package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js.map → sanitizeSvg-NCGPTJ3E.js.map} +0 -0
  56. /package/packages/tldraw5/dist/{chunk-GKLJTYCF.js.map → chunk-DO47QRII.js.map} +0 -0
  57. /package/packages/tldraw5/dist/{chunk-4JN3PIR7.js.map → chunk-FTKY6DDS.js.map} +0 -0
  58. /package/packages/tldraw5/dist/{chunk-JDHJ334K.js.map → chunk-SZI4VCOI.js.map} +0 -0
  59. /package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js.map → sanitizeSvg-J7F3MQGT.js.map} +0 -0
  60. /package/packages/tldraw5/dist/{tldr-archive-WERHN3T4.js.map → tldr-archive-5X7EMHWZ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DraftStateProvider-Bhz0S4c1.js","sources":["../../src/actor-attribution.ts","../../src/draft-routing.ts","../../src/providers/DraftStateProvider.ts"],"sourcesContent":["import {\n isValidAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type DocHandleChangePayload,\n type Repo,\n} from \"@automerge/automerge-repo/slim\";\nimport * as Automerge from \"@automerge/automerge/slim\";\nimport { subscribe } from \"@inkandswitch/patchwork-providers\";\n\nimport type { ActorAttributionDoc, DraftDoc } from \"./draft-types.js\";\nimport { editCountsSince } from \"./change-group-cache.js\";\n\n// Only the writing client knows which Automerge actor ids are its own. The\n// recorder watches the docs the user can currently write to (`watch`); each\n// local change reveals one id, which it attributes to the current user's\n// contact. The attribution doc is resolved through `resolveAttribution` only\n// once there is an id to record, so viewing a document creates nothing.\n// Changes seen before dependencies resolve are buffered.\nexport function createActorRecorder(\n element: HTMLElement,\n repo: Repo,\n resolveAttribution: () => Promise<DocHandle<ActorAttributionDoc> | null>\n): ActorRecorder {\n let contactUrl: AutomergeUrl | null = null;\n let attributionHandle: DocHandle<ActorAttributionDoc> | null = null;\n let attribution: Promise<void> | null = null;\n const recordedActorIds = new Set<string>();\n const pendingActorIds = new Set<string>();\n const watched = new Map<AutomergeUrl, DocHandle<unknown> | null>();\n let disposed = false;\n\n const unsubscribe = subscribe<AutomergeUrl>(\n element,\n { type: CONTACT_SELECTOR },\n (value) => {\n if (disposed || contactUrl) return;\n if (typeof value === \"string\" && isValidAutomergeUrl(value)) {\n contactUrl = value;\n flushPendingActors();\n }\n }\n );\n\n // `patchInfo.source` is \"change\" for remote updates too in this\n // automerge-repo build, so a local write is recognised by its actor: one of\n // the new changes was made by this doc instance's own actor id. Changes that\n // only touch `@patchwork` (tools stamping metadata on open) don't count.\n const onChange = (payload: DocHandleChangePayload<unknown>) => {\n if (disposed) return;\n const { before, after } = payload.patchInfo;\n const actorId = Automerge.getActorId(after);\n if (recordedActorIds.has(actorId)) return;\n const since = Automerge.getHeads(before);\n const ours = Automerge.getChangesMetaSince(after, since).filter(\n (meta) => meta.actor === actorId\n );\n if (ours.length === 0) return;\n void editCountsSince(after, since).then(\n (counts) => {\n if (disposed || !counts) return;\n const edited = ours.some((meta) => {\n const c = counts.get(meta.hash);\n return !!c && c.additions + c.deletions > 0;\n });\n if (!edited) return;\n pendingActorIds.add(actorId);\n flushPendingActors();\n },\n () => {}\n );\n };\n\n return {\n watch(urls) {\n if (disposed) return;\n const wanted = new Set(urls);\n for (const [url, handle] of [...watched]) {\n if (wanted.has(url)) continue;\n handle?.off(\"change\", onChange);\n watched.delete(url);\n }\n for (const url of wanted) {\n if (watched.has(url)) continue;\n watched.set(url, null);\n repo.find<unknown>(url).then(\n (handle) => {\n if (disposed || watched.get(url) !== null) return;\n watched.set(url, handle);\n handle.on(\"change\", onChange);\n },\n () => {\n if (watched.get(url) === null) watched.delete(url);\n }\n );\n }\n },\n dispose() {\n disposed = true;\n pendingActorIds.clear();\n for (const [, handle] of watched) handle?.off(\"change\", onChange);\n watched.clear();\n unsubscribe();\n },\n };\n\n function flushPendingActors(): void {\n if (disposed || !contactUrl || pendingActorIds.size === 0) return;\n if (!attributionHandle) {\n attribution ??= resolveAttribution().then(\n (handle) => {\n if (disposed) return;\n attributionHandle = handle;\n if (handle) flushPendingActors();\n },\n (err) => {\n attribution = null;\n console.warn(\"[drafts] failed to resolve actor attribution:\", err);\n }\n );\n return;\n }\n const url = contactUrl;\n const actorIds = [...pendingActorIds];\n pendingActorIds.clear();\n for (const id of actorIds) recordedActorIds.add(id);\n const existing = attributionHandle.doc()?.actors ?? {};\n const missing = actorIds.filter((id) => existing[id] !== url);\n if (missing.length === 0) return;\n attributionHandle.change((d) => {\n for (const id of missing) d.actors[id] = url;\n });\n }\n}\n\n// Resolve the host doc's actor-attribution doc, creating it and stamping\n// `actorAttributionUrl` on the main draft the first time. One per host doc\n// (actor ids span main and every draft), following the same check-then-create\n// pattern as `ensureChangeGroupDoc`: the rare concurrent-create orphan is\n// accepted.\nexport async function ensureActorAttribution(\n repo: Repo,\n mainDraftHandle: DocHandle<DraftDoc>\n): Promise<DocHandle<ActorAttributionDoc>> {\n const existingUrl = mainDraftHandle.doc()?.actorAttributionUrl;\n if (existingUrl && isValidAutomergeUrl(existingUrl)) {\n return repo.find<ActorAttributionDoc>(existingUrl);\n }\n\n const attribution = repo.create<ActorAttributionDoc>({\n \"@patchwork\": { type: \"actor-attribution\" },\n actors: {},\n });\n mainDraftHandle.change((d) => {\n if (!d.actorAttributionUrl) d.actorAttributionUrl = attribution.url;\n });\n // A concurrent creator may have won the stamp; honor whichever pointer\n // settled (our fresh doc is then an accepted orphan, like a group doc).\n const settled = mainDraftHandle.doc()?.actorAttributionUrl;\n if (settled && settled !== attribution.url && isValidAutomergeUrl(settled)) {\n return repo.find<ActorAttributionDoc>(settled);\n }\n return attribution;\n}\n\nexport type ActorRecorder = {\n watch: (urls: readonly AutomergeUrl[]) => void;\n dispose: () => void;\n};\n\nconst CONTACT_SELECTOR = \"patchwork:contact\";\n","import {\n isValidAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n} from \"@automerge/automerge-repo/slim\";\n\nimport type { CheckedOutDraft } from \"./draft-types.js\";\n\n// Keeps the ephemeral checkout selection and the `draft=` URL parameter in\n// sync. Deep links remain pending until their draft appears in the synced tree.\nexport function createDraftRouter(\n checkedOutHandle: DocHandle<CheckedOutDraft>\n): DraftRouter {\n let availableDrafts = new Set<AutomergeUrl>();\n let pendingUrlDraft: AutomergeUrl | null = readDraftParam();\n let lastCheckedOut: AutomergeUrl | null = null;\n let disposed = false;\n\n checkedOutHandle.on(\"change\", syncSelectionToUrl);\n window.addEventListener(\"hashchange\", onHashChange);\n\n return {\n updateAvailableDrafts(draftUrls) {\n if (disposed) return;\n availableDrafts = new Set(draftUrls);\n reconcileSelection();\n },\n dispose() {\n disposed = true;\n checkedOutHandle.off(\"change\", syncSelectionToUrl);\n window.removeEventListener(\"hashchange\", onHashChange);\n },\n };\n\n function onHashChange(): void {\n if (disposed) return;\n const urlDraft = readDraftParam();\n const selected = checkedOutHandle.doc()?.checkedOut ?? null;\n if (urlDraft === selected) {\n pendingUrlDraft = null;\n return;\n }\n if (urlDraft === null) {\n pendingUrlDraft = null;\n selectDraft(null);\n return;\n }\n if (availableDrafts.has(urlDraft)) {\n pendingUrlDraft = null;\n selectDraft(urlDraft);\n return;\n }\n pendingUrlDraft = urlDraft;\n }\n\n function reconcileSelection(): void {\n if (pendingUrlDraft && availableDrafts.has(pendingUrlDraft)) {\n const target = pendingUrlDraft;\n pendingUrlDraft = null;\n selectDraft(target);\n return;\n }\n const selected = checkedOutHandle.doc()?.checkedOut ?? null;\n if (selected && !availableDrafts.has(selected)) selectDraft(null);\n }\n\n function selectDraft(selected: AutomergeUrl | null): void {\n if (checkedOutHandle.doc()?.checkedOut === selected) return;\n checkedOutHandle.change((d) => {\n d.checkedOut = selected;\n });\n }\n\n function syncSelectionToUrl(): void {\n if (disposed) return;\n const selected = checkedOutHandle.doc()?.checkedOut ?? null;\n if (selected === lastCheckedOut) return;\n lastCheckedOut = selected;\n if (pendingUrlDraft && selected !== pendingUrlDraft) pendingUrlDraft = null;\n writeDraftParam(selected);\n }\n}\n\nexport type DraftRouter = {\n updateAvailableDrafts: (draftUrls: readonly AutomergeUrl[]) => void;\n dispose: () => void;\n};\n\nconst DRAFT_PARAM = \"draft\";\nconst RAW_HASH_KEYS = new Set([\"doc\", DRAFT_PARAM]);\n\nfunction readDraftParam(): AutomergeUrl | null {\n const raw = new URLSearchParams(window.location.hash.slice(1)).get(\n DRAFT_PARAM\n );\n return raw && isValidAutomergeUrl(raw) ? raw : null;\n}\n\nfunction writeDraftParam(selected: AutomergeUrl | null): void {\n const params = new URLSearchParams(window.location.hash.slice(1));\n if ((params.get(DRAFT_PARAM) ?? null) === selected) return;\n if (selected) params.set(DRAFT_PARAM, selected);\n else params.delete(DRAFT_PARAM);\n try {\n history.replaceState(null, \"\", \"#\" + serializeHash(params));\n } catch {\n // Sandboxed realms can refuse replaceState; the URL is cosmetic there.\n }\n}\n\nfunction serializeHash(params: URLSearchParams): string {\n const parts: string[] = [];\n params.forEach((value, key) => {\n if (!value) return;\n parts.push(\n `${key}=${RAW_HASH_KEYS.has(key) ? value : encodeURIComponent(value)}`\n );\n });\n return parts.join(\"&\");\n}\n","import {\n encodeHeads,\n isValidAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type Repo,\n type UrlHeads,\n} from \"@automerge/automerge-repo/slim\";\nimport { accept, type SubscribeEvent } from \"@inkandswitch/patchwork-providers\";\nimport type {\n MountedEvent,\n UnmountedEvent,\n} from \"@inkandswitch/patchwork-elements\";\n\nimport type {\n ActorAttributionDoc,\n Baseline,\n CheckedOutDraft,\n CloneEntry,\n DraftDoc,\n DraftList,\n DraftMemberDoc,\n DraftSummary,\n HasDrafts,\n} from \"../draft-types.js\";\nimport { SKIPPED_DATATYPES, canonicalUrl } from \"../clone-policy.js\";\nimport {\n createChangeGrouper,\n type TimelineGroupingSpec,\n} from \"../change-group-cache.js\";\nimport {\n createActorRecorder,\n ensureActorAttribution,\n} from \"../actor-attribution.js\";\nimport { createDraftRouter } from \"../draft-routing.js\";\nimport { ensureMainDraft } from \"../draft-docs.js\";\n\nconst ROOT_DOC_SELECTOR = \"draft:root-doc\";\nconst CHECKED_OUT_SELECTOR = \"draft:checked-out\";\nconst DRAFT_LIST_SELECTOR = \"draft:list\";\nconst BASELINE_SELECTOR = \"draft:baseline\";\n\nconst ATTR_DOC_URL = \"doc-url\";\n\n// Fork point recorded for the main draft's identity clones: empty heads means\n// \"from the start\", so `getChangesMetaSince(doc, [])` yields the full history.\nconst EMPTY_HEADS: UrlHeads = encodeHeads([]);\n\n// One ephemeral CheckedOutDraft per host doc for the life of this realm. The\n// host remounts this provider on every doc switch; a fresh doc per mount\n// would be stored and synced to every peer each time (delete is local-only),\n// so the doc is kept and reset to main on remount instead.\nconst checkoutDocs = new Map<AutomergeUrl, DocHandle<CheckedOutDraft>>();\n\nfunction checkoutFor(\n repo: Repo,\n docUrl: AutomergeUrl\n): DocHandle<CheckedOutDraft> {\n let handle = checkoutDocs.get(docUrl);\n if (!handle) {\n handle = repo.create<CheckedOutDraft>({ checkedOut: null });\n checkoutDocs.set(docUrl, handle);\n return handle;\n }\n const doc = handle.doc();\n if (doc?.checkedOut != null || doc?.at != null) {\n handle.change((d) => {\n d.checkedOut = null;\n d.at = null;\n });\n }\n return handle;\n}\n\n// Mounts on a document URL and exposes that document's draft state via three\n// subscriptions:\n// - `draft:root-doc` → AutomergeUrl of the doc this provider is on\n// - `draft:checked-out` → AutomergeUrl of the ephemeral, writeable\n// CheckedOutDraft doc; holds only the selection (`checkedOut = null` = main)\n// - `draft:list` → DraftList: the read-only main entry plus one\n// `DraftSummary` per non-merged draft, each carrying its member docs (on a\n// draft, the forked docs from `DraftDoc.clones`; on main, the main draft's\n// identity clones, or — before the first draft — the docs mounted beneath\n// this provider observed via `patchwork:mounted`, fork fields `null`).\n//\n// Consumers recover the live `DocHandle`s from the realm-local `window.repo`,\n// so member entries carry only plain `AutomergeUrl`s. The link from a host doc\n// to its drafts is `@patchwork.mainDraftUrl` → the main draft, whose `drafts`\n// roots the tree; each `DraftDoc` may have its own sub-drafts via\n// `DraftDoc.drafts`.\n//\n// Viewing a document costs nothing persistent. The host doc's draft\n// bookkeeping (its main draft and ActorAttributionDoc) is created on demand:\n// when the sidebar first subscribes to `draft:list`, or when the user first\n// edits a doc beneath us. Timelines are grouped only while a `draft:list`\n// subscriber exists.\nexport const DraftStateProvider = (element: HTMLElement) => {\n const rawUrl = element.getAttribute(ATTR_DOC_URL);\n if (!rawUrl || !isValidAutomergeUrl(rawUrl)) {\n console.warn(\n `[drafts] <patchwork-view component=\"patchwork-draft-state-provider\"> ` +\n `is missing a valid ${ATTR_DOC_URL} attribute (got ${JSON.stringify(rawUrl)})`\n );\n return () => {};\n }\n const docUrl: AutomergeUrl = rawUrl;\n\n const maybeRepo = \"repo\" in window ? window.repo : undefined;\n if (!maybeRepo) {\n console.warn(\n \"[drafts] window.repo is not set; draft-state provider disabled\"\n );\n return () => {};\n }\n const repo: Repo = maybeRepo;\n\n let hostDocHandle: DocHandle<HasDrafts> | null = null;\n const trackedDrafts = new Map<AutomergeUrl, DocHandle<DraftDoc>>();\n // The host doc's single main draft (bookkeeping only). Resolved lazily from\n // `@patchwork.mainDraftUrl`; its `drafts` roots the draft tree and its\n // identity `clones` back the \"main\" member list.\n let mainDraftHandle: DocHandle<DraftDoc> | null = null;\n\n const checkedOutHandle = checkoutFor(repo, docUrl);\n const draftRouter = createDraftRouter(checkedOutHandle);\n\n // `draft:baseline` subscribers, keyed by canonical target url. This provider\n // is the sole answerer (the overlay no longer claims it), serving the\n // checkpoint's per-doc `from` (see `currentBaseline`). Re-emitted whenever\n // the checkout doc changes.\n const baselineSubscribers = new Map<\n AutomergeUrl,\n Set<(baseline: Baseline) => void>\n >();\n const sentBaselines = new Map<AutomergeUrl, UrlHeads | null>();\n\n // `draft:list` bookkeeping: the last computed list, its live subscribers, and\n // the draft order from the most recent rewalk.\n const listSubscribers = new Set<(list: DraftList) => void>();\n let orderedDraftUrls: AutomergeUrl[] = [];\n let draftList: DraftList = {\n main: {\n url: docUrl,\n parent: null,\n members: [],\n childCount: 0,\n name: null,\n changeGroupDocUrl: null,\n },\n drafts: [],\n actorAttributionUrl: null,\n };\n // Author attribution: stamps actor ids from local member-doc changes into\n // the host doc's shared ActorAttributionDoc.\n const actorRecorder = createActorRecorder(element, repo, ensureAttribution);\n // Groups each timeline into its ChangeGroupDoc while the sidebar is open.\n const changeGrouper = createChangeGrouper(repo);\n // Main-case membership: docs mounted beneath this provider, ref-counted so a\n // doc shown in several views is only dropped on its last unmount. Populated\n // even while a draft is selected (where it goes unused) so switching back to\n // main is instant.\n const mountCounts = new Map<AutomergeUrl, number>();\n // Cached \"is this an app-global datatype we skip?\" verdict per mounted url,\n // resolved lazily since reading `@patchwork.type` means loading the doc.\n // Absent = unresolved, treated as not-skipped (visible) until known.\n const skipVerdicts = new Map<AutomergeUrl, boolean>();\n\n let disposed = false;\n let rewalkInFlight = false;\n let rewalkPending = false;\n let cloneSyncTimer: ReturnType<typeof setTimeout> | null = null;\n let mainDraftReady: Promise<DocHandle<DraftDoc> | null> | null = null;\n let attributionReady: Promise<DocHandle<ActorAttributionDoc> | null> | null =\n null;\n // A tracked draft changing can mean either its sub-draft list moved (needs a\n // rewalk) or its clone map grew (needs a list recompute), so do both.\n const onTrackedChange = () => {\n scheduleRewalk();\n reconcile();\n };\n const onHostDocChange = () => {\n const url = hostDocHandle?.doc()?.[\"@patchwork\"]?.mainDraftUrl ?? null;\n if (url !== (mainDraftHandle?.url ?? null)) scheduleRewalk();\n };\n // The checkout doc changed: its checkpoint may have moved, so re-publish\n // every live `draft:baseline` subscriber.\n const onCheckedOutChange = () => {\n notifyBaselines();\n reconcile();\n };\n\n const onMounted = (event: MountedEvent) => {\n const detail = event.detail;\n if (!(\"url\" in detail)) return;\n const url = canonicalUrl(detail.url);\n mountCounts.set(url, (mountCounts.get(url) ?? 0) + 1);\n ensureSkipVerdict(url);\n scheduleCloneSync();\n reconcile();\n };\n\n const onUnmounted = (event: UnmountedEvent) => {\n const detail = event.detail;\n if (!(\"url\" in detail)) return;\n const url = canonicalUrl(detail.url);\n const count = mountCounts.get(url) ?? 0;\n if (count <= 1) mountCounts.delete(url);\n else mountCounts.set(url, count - 1);\n reconcile();\n };\n\n const ready: Promise<void> = (async () => {\n const handle = await repo.find<HasDrafts>(docUrl);\n if (disposed) return;\n hostDocHandle = handle;\n handle.on(\"change\", onHostDocChange);\n scheduleRewalk();\n reconcile();\n })();\n ready.catch((err) => {\n console.error(`[drafts] failed to initialize draft-state provider:`, err);\n });\n\n checkedOutHandle.on(\"change\", onCheckedOutChange);\n\n const onSubscribe = (event: SubscribeEvent) => {\n const { type } = event.detail.selector;\n\n if (type === ROOT_DOC_SELECTOR) {\n accept<AutomergeUrl>(event, (respond) => {\n respond(docUrl);\n });\n return;\n }\n\n if (type === CHECKED_OUT_SELECTOR) {\n accept<AutomergeUrl>(event, (respond) => {\n respond(checkedOutHandle.url);\n });\n return;\n }\n\n if (type === DRAFT_LIST_SELECTOR) {\n // `draft:list` answers with a DraftList *object*, never an AutomergeUrl, so\n // consumers must use `subscribe` (not `subscribeDoc`, which would feed this\n // object into `repo.find` and crash with \"Invalid AutomergeUrl\").\n accept<DraftList>(event, (respond) => {\n respond(draftList);\n listSubscribers.add(respond);\n if (listSubscribers.size === 1) activate();\n return () => {\n listSubscribers.delete(respond);\n if (listSubscribers.size === 0) deactivate();\n };\n });\n return;\n }\n\n if (type === BASELINE_SELECTOR) {\n // Sole answerer for `draft:baseline` (the overlay no longer claims it):\n // serves the checkpoint's per-doc `from` for `target` (see\n // `currentBaseline`).\n const rawTarget = (event.detail.selector as { url?: unknown }).url;\n if (typeof rawTarget !== \"string\" || !isValidAutomergeUrl(rawTarget)) {\n return;\n }\n const target = canonicalUrl(rawTarget);\n accept<Baseline>(event, (respond) => {\n const baseline = currentBaseline(target);\n sentBaselines.set(target, baseline.heads);\n respond(baseline);\n let set = baselineSubscribers.get(target);\n if (!set) baselineSubscribers.set(target, (set = new Set()));\n set.add(respond);\n return () => {\n set!.delete(respond);\n if (set!.size === 0) {\n baselineSubscribers.delete(target);\n sentBaselines.delete(target);\n }\n };\n });\n return;\n }\n };\n\n element.addEventListener(\"patchwork:subscribe\", onSubscribe);\n element.addEventListener(\"patchwork:mounted\", onMounted);\n element.addEventListener(\"patchwork:unmounted\", onUnmounted);\n\n return () => {\n disposed = true;\n if (cloneSyncTimer !== null) clearTimeout(cloneSyncTimer);\n changeGrouper.dispose();\n actorRecorder.dispose();\n draftRouter.dispose();\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n element.removeEventListener(\"patchwork:mounted\", onMounted);\n element.removeEventListener(\"patchwork:unmounted\", onUnmounted);\n hostDocHandle?.off(\"change\", onHostDocChange);\n mainDraftHandle?.off(\"change\", onTrackedChange);\n for (const [, h] of trackedDrafts) h.off(\"change\", onTrackedChange);\n checkedOutHandle.off(\"change\", onCheckedOutChange);\n mainDraftHandle = null;\n trackedDrafts.clear();\n listSubscribers.clear();\n baselineSubscribers.clear();\n sentBaselines.clear();\n mountCounts.clear();\n skipVerdicts.clear();\n hostDocHandle = null;\n };\n\n // The sidebar is open: make sure the host doc has its main draft (so the\n // Main timeline has a home), record what's mounted as main's members, and\n // keep the timelines grouped.\n function activate(): void {\n ensureMainDraftDoc().then(\n () => {\n scheduleCloneSync();\n reconcile();\n },\n (err) => console.warn(\"[drafts] failed to create main draft:\", err)\n );\n reconcile();\n }\n\n function deactivate(): void {\n changeGrouper.setTimelines([]);\n }\n\n // Resolve (creating on first use) the host doc's main draft. Main's clones\n // are identity mappings — nothing is forked; the only host-doc side effect\n // is the `mainDraftUrl` scalar, which the timeline's `@patchwork` path skip\n // filters out. App-global datatypes the draft machinery never treats as\n // content are left alone (`null`). A failed attempt is retried next time.\n function ensureMainDraftDoc(): Promise<DocHandle<DraftDoc> | null> {\n if (mainDraftReady) return mainDraftReady;\n const attempt = (async () => {\n await ready;\n if (disposed || !hostDocHandle) return null;\n const hostType = hostDocHandle.doc()?.[\"@patchwork\"]?.type;\n if (hostType != null && SKIPPED_DATATYPES.has(hostType)) return null;\n const mainDraft = await ensureMainDraft(repo, hostDocHandle);\n if (disposed) return null;\n scheduleRewalk();\n return mainDraft;\n })();\n mainDraftReady = attempt;\n attempt.catch(() => {\n if (mainDraftReady === attempt) mainDraftReady = null;\n });\n return attempt;\n }\n\n // The host doc's ActorAttributionDoc, created (with the main draft it hangs\n // off) on the first local edit — only writers need it.\n function ensureAttribution(): Promise<DocHandle<ActorAttributionDoc> | null> {\n if (attributionReady) return attributionReady;\n const attempt = ensureMainDraftDoc().then((mainDraft) =>\n mainDraft && !disposed ? ensureActorAttribution(repo, mainDraft) : null\n );\n attributionReady = attempt;\n attempt.catch(() => {\n if (attributionReady === attempt) attributionReady = null;\n });\n return attempt;\n }\n\n function scheduleRewalk(): void {\n if (disposed) return;\n if (!hostDocHandle) return;\n if (rewalkInFlight) {\n rewalkPending = true;\n return;\n }\n rewalkInFlight = true;\n void (async () => {\n try {\n // Drafts hang off the main draft (`mainDraft.drafts`), which is created\n // on the first draft; until then there are none.\n const mainDraft = await ensureMainDraftTracked();\n if (disposed) return;\n const roots = (mainDraft?.doc()?.drafts ?? []).filter(\n isValidAutomergeUrl\n );\n const allDrafts = await collectAllDrafts(\n repo,\n roots,\n trackedDrafts,\n onTrackedChange,\n () => disposed\n );\n if (disposed) return;\n const reachable = new Set(allDrafts);\n for (const [url, handle] of trackedDrafts) {\n if (reachable.has(url)) continue;\n handle.off(\"change\", onTrackedChange);\n trackedDrafts.delete(url);\n }\n orderedDraftUrls = allDrafts;\n draftRouter.updateAvailableDrafts(allDrafts);\n } catch (err) {\n console.error(\"[drafts] rewalk failed:\", err);\n } finally {\n rewalkInFlight = false;\n // A rewalk may have just started tracking a draft (whose clones won't\n // fire their own change event), so refresh the list.\n reconcile();\n if (rewalkPending) {\n rewalkPending = false;\n scheduleRewalk();\n }\n }\n })();\n }\n\n // Bring everything derived from the current state up to date: which docs\n // the actor recorder watches, which timelines are grouped (sidebar open\n // only), and the `draft:list` value, pushed to subscribers when it actually\n // changed.\n function reconcile(): void {\n if (disposed) return;\n actorRecorder.watch(writableDocUrls());\n if (listSubscribers.size > 0) updateChangeGrouping();\n const next = computeList();\n if (draftListsEqual(draftList, next)) return;\n draftList = next;\n for (const respond of listSubscribers) respond(next);\n }\n\n // The docs the user can write to right now: the mounted originals on main,\n // plus the checked-out draft's clones, where the overlay routes edits.\n function writableDocUrls(): AutomergeUrl[] {\n const urls = [...mountCounts.keys()].filter(\n (url) => skipVerdicts.get(url) === false\n );\n const selected = checkedOutHandle.doc()?.checkedOut ?? null;\n const clones = selected\n ? trackedDrafts.get(selected)?.doc()?.clones\n : undefined;\n for (const entry of Object.values(clones ?? {})) {\n urls.push(canonicalUrl(entry.cloneUrl));\n }\n return urls;\n }\n\n // Hand the ChangeGrouper the current timelines, priority-ordered: main,\n // checked-out draft, then the remaining drafts in tree order. Main reads\n // the main draft's identity clones rather than the mounted-doc fallback.\n function updateChangeGrouping(): void {\n if (disposed) return;\n const specs: TimelineGroupingSpec[] = [];\n if (mainDraftHandle) {\n specs.push({\n draftHandle: mainDraftHandle,\n members: clonesToMembers(mainDraftHandle.doc()?.clones ?? {}),\n rootDocUrl: docUrl,\n });\n }\n const selected = checkedOutHandle.doc()?.checkedOut ?? null;\n const ordered =\n selected && orderedDraftUrls.includes(selected)\n ? [selected, ...orderedDraftUrls.filter((u) => u !== selected)]\n : orderedDraftUrls;\n for (const url of ordered) {\n const handle = trackedDrafts.get(url);\n const doc = handle?.doc();\n if (!handle || !doc || doc.mergedAt !== undefined) continue;\n specs.push({\n draftHandle: handle,\n members: clonesToMembers(doc.clones),\n rootDocUrl: docUrl,\n });\n }\n changeGrouper.setTimelines(specs);\n }\n\n // The full read-only list: the main entry plus one summary per non-merged\n // draft, in rewalk (tree) order.\n function computeList(): DraftList {\n const drafts: DraftSummary[] = [];\n for (const url of orderedDraftUrls) {\n const doc = trackedDrafts.get(url)?.doc();\n if (!doc || doc.mergedAt !== undefined) continue;\n drafts.push({\n url,\n parent: effectiveParent(doc.parent),\n members: clonesToMembers(doc.clones),\n childCount: doc.drafts.length,\n name: doc.name ?? null,\n changeGroupDocUrl: doc.changeGroupDocUrl ?? null,\n });\n }\n return {\n main: computeMainSummary(),\n drafts,\n actorAttributionUrl:\n mainDraftHandle?.doc()?.actorAttributionUrl ?? null,\n };\n }\n\n // The parent a summary should point at: the nearest non-merged ancestor.\n // Merging normally re-parents children, but drafts orphaned before that\n // existed (or by a concurrent merge on another peer) still resolve to the\n // draft their parent was merged into instead of dangling under a hidden\n // one. Unknown urls (not tracked, e.g. the main draft) pass through as-is.\n function effectiveParent(\n parentUrl: AutomergeUrl | undefined\n ): AutomergeUrl | null {\n const seen = new Set<AutomergeUrl>();\n let cursor = parentUrl ?? null;\n while (cursor && !seen.has(cursor)) {\n seen.add(cursor);\n const doc = trackedDrafts.get(cursor)?.doc();\n if (!doc || doc.mergedAt === undefined) return cursor;\n cursor = doc.parent ?? null;\n }\n return cursor;\n }\n\n // Main's summary. Its members come from the main draft's identity clones once\n // it exists; before the first draft is created (no main draft) we fall back to\n // the docs mounted beneath us, minus the app-global datatypes the overlay\n // would never fork. Members are sorted by url so the diff above is positional.\n function computeMainSummary(): DraftSummary {\n const url = mainDraftHandle?.url ?? docUrl;\n const childCount = mainDraftHandle?.doc()?.drafts.length ?? 0;\n const name = mainDraftHandle?.doc()?.name ?? null;\n const changeGroupDocUrl =\n mainDraftHandle?.doc()?.changeGroupDocUrl ?? null;\n\n const mainClones = mainDraftHandle?.doc()?.clones;\n if (mainClones && Object.keys(mainClones).length > 0) {\n return {\n url,\n parent: null,\n members: clonesToMembers(mainClones),\n childCount,\n name,\n changeGroupDocUrl,\n };\n }\n\n const members = [...mountCounts.keys()]\n .filter((u) => skipVerdicts.get(u) !== true)\n .map((u) => ({ url: u, cloneUrl: null, clonedAt: null }))\n .sort(byMemberUrl);\n return { url, parent: null, members, childCount, name, changeGroupDocUrl };\n }\n\n // The diff baseline for `target`: the checkpoint's per-doc `from`, written\n // by the sidebar (its eye toggle and scrubber). `null` — no entry, or an\n // entry without `from` — means no diff. There is deliberately no implicit\n // fork-point fallback: baselines exist only when explicitly written into\n // the checkpoint, so \"diffs hidden\" is simply their absence.\n function currentBaseline(target: AutomergeUrl): Baseline {\n const entry = checkedOutHandle.doc()?.at?.[target];\n return { heads: entry?.from ?? null };\n }\n\n // Re-answer only the targets whose baseline actually moved: a re-send makes\n // every editor on that doc recompute its diff.\n function notifyBaselines(): void {\n for (const [target, set] of baselineSubscribers) {\n const baseline = currentBaseline(target);\n if (sameHeads(baseline.heads, sentBaselines.get(target) ?? null)) {\n continue;\n }\n sentBaselines.set(target, baseline.heads);\n for (const respond of [...set]) respond(baseline);\n }\n }\n\n // Resolve (once, cached) whether a mounted doc is an app-global datatype we\n // exclude from the main-case membership. On failure we leave it unresolved,\n // so the doc stays visible — mirroring the overlay's \"fall back to forking\".\n function ensureSkipVerdict(url: AutomergeUrl): void {\n if (skipVerdicts.has(url)) return;\n void (async () => {\n try {\n const handle = await repo.find<HasDrafts>(url);\n if (disposed) return;\n const type = handle.doc()?.[\"@patchwork\"]?.type;\n const skipped = type != null && SKIPPED_DATATYPES.has(type);\n if (skipVerdicts.get(url) === skipped) return;\n skipVerdicts.set(url, skipped);\n // A now-confirmed not-skipped doc may belong in the main draft.\n scheduleCloneSync();\n reconcile();\n } catch {\n // Leave unresolved: the doc keeps showing up, which is the safe default.\n }\n })();\n }\n\n // Resolve and start tracking the host doc's main draft, if any. Returns the\n // handle, or null when the host doc has no `mainDraftUrl` yet. Re-resolves\n // when the pointer changes and attaches `onTrackedChange` so the main draft's\n // `drafts` (tree shape) and `clones` (main membership) stay live.\n async function ensureMainDraftTracked(): Promise<DocHandle<DraftDoc> | null> {\n if (!hostDocHandle) return null;\n const mainDraftUrl = hostDocHandle.doc()?.[\"@patchwork\"]?.mainDraftUrl;\n if (!mainDraftUrl || !isValidAutomergeUrl(mainDraftUrl)) return null;\n if (mainDraftHandle && mainDraftHandle.url === mainDraftUrl) {\n return mainDraftHandle;\n }\n if (mainDraftHandle) mainDraftHandle.off(\"change\", onTrackedChange);\n const handle = await repo.find<DraftDoc>(mainDraftUrl);\n if (disposed) return null;\n mainDraftHandle = handle;\n handle.on(\"change\", onTrackedChange);\n syncMainDraftClones();\n return handle;\n }\n\n // Mounts and skip verdicts arrive one doc at a time; batch them into a single\n // main-draft write (each write would otherwise rewalk and regroup).\n function scheduleCloneSync(): void {\n if (cloneSyncTimer !== null) return;\n cloneSyncTimer = setTimeout(() => {\n cloneSyncTimer = null;\n syncMainDraftClones();\n }, 0);\n }\n\n // Keep the main draft's identity clone map in step with the live mounted set\n // while the sidebar is open: every confirmed not-skipped mounted doc gets an\n // identity entry (`cloneUrl === url`, empty fork heads). Additive only —\n // entries are never removed, so main's membership (and history) is stable\n // across unmounts. Writes are diffed, so this is a no-op once everything\n // mounted is already recorded.\n function syncMainDraftClones(): void {\n if (disposed || !mainDraftHandle || listSubscribers.size === 0) return;\n const existing = mainDraftHandle.doc()?.clones ?? {};\n const toAdd = [...mountCounts.keys()].filter(\n (url) => skipVerdicts.get(url) === false && !existing[url]\n );\n if (toAdd.length === 0) return;\n mainDraftHandle.change((d) => {\n for (const url of toAdd) {\n if (!d.clones[url]) {\n d.clones[url] = { cloneUrl: url, clonedAt: EMPTY_HEADS };\n }\n }\n });\n }\n};\n\n// Depth-first pre-order, so a fork lists directly beneath the draft it was\n// forked off (the sidebar indents by parent depth).\nasync function collectAllDrafts(\n repo: Repo,\n roots: readonly AutomergeUrl[],\n tracked: Map<AutomergeUrl, DocHandle<DraftDoc>>,\n onNewChange: () => void,\n isDisposed: () => boolean\n): Promise<AutomergeUrl[]> {\n const visited = new Set<AutomergeUrl>();\n const order: AutomergeUrl[] = [];\n const queue: AutomergeUrl[] = [...roots];\n while (queue.length) {\n const url = queue.shift()!;\n if (visited.has(url)) continue;\n visited.add(url);\n order.push(url);\n\n let h = tracked.get(url);\n if (!h) {\n h = await repo.find<DraftDoc>(url);\n if (isDisposed()) return order;\n tracked.set(url, h);\n h.on(\"change\", onNewChange);\n }\n const drafts = (h.doc()?.drafts ?? []).filter(isValidAutomergeUrl);\n queue.unshift(...drafts);\n }\n return order;\n}\n\n// Project a clone map into member-doc entries, sorted by url so the positional\n// equality checks below stay valid.\nfunction clonesToMembers(\n clones: Record<AutomergeUrl, CloneEntry>\n): DraftMemberDoc[] {\n return Object.entries(clones)\n .map(([url, entry]) => ({\n url: url as AutomergeUrl,\n cloneUrl: entry.cloneUrl,\n clonedAt: entry.clonedAt,\n }))\n .sort(byMemberUrl);\n}\n\nfunction draftListsEqual(a: DraftList, b: DraftList): boolean {\n if (a.actorAttributionUrl !== b.actorAttributionUrl) return false;\n if (!summariesEqual(a.main, b.main)) return false;\n if (a.drafts.length !== b.drafts.length) return false;\n for (let i = 0; i < a.drafts.length; i++) {\n if (!summariesEqual(a.drafts[i], b.drafts[i])) return false;\n }\n return true;\n}\n\nfunction summariesEqual(a: DraftSummary, b: DraftSummary): boolean {\n return (\n a.url === b.url &&\n a.parent === b.parent &&\n a.childCount === b.childCount &&\n a.name === b.name &&\n a.changeGroupDocUrl === b.changeGroupDocUrl &&\n memberListsEqual(a.members, b.members)\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\nfunction memberListsEqual(a: DraftMemberDoc[], b: DraftMemberDoc[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i].url !== b[i].url) return false;\n if (a[i].cloneUrl !== b[i].cloneUrl) return false;\n if (!sameHeads(a[i].clonedAt, b[i].clonedAt)) return false;\n }\n return true;\n}\n\nfunction sameHeads(a: UrlHeads | null, b: UrlHeads | null): 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"],"names":[],"mappings":";;;;;AAmBO,SAAS,oBACd,SACA,MACA,oBACe;AACf,MAAI,aAAkC;AACtC,MAAI,oBAA2D;AAC/D,MAAI,cAAoC;AACxC,QAAM,uCAAuB,IAAA;AAC7B,QAAM,sCAAsB,IAAA;AAC5B,QAAM,8BAAc,IAAA;AACpB,MAAI,WAAW;AAEf,QAAM,cAAc;AAAA,IAClB;AAAA,IACA,EAAE,MAAM,iBAAA;AAAA,IACR,CAAC,UAAU;AACT,UAAI,YAAY,WAAY;AAC5B,UAAI,OAAO,UAAU,YAAY,oBAAoB,KAAK,GAAG;AAC3D,qBAAa;AACb,2BAAA;AAAA,MACF;AAAA,IACF;AAAA,EAAA;AAOF,QAAM,WAAW,CAAC,YAA6C;AAC7D,QAAI,SAAU;AACd,UAAM,EAAE,QAAQ,MAAA,IAAU,QAAQ;AAClC,UAAM,UAAU,UAAU,WAAW,KAAK;AAC1C,QAAI,iBAAiB,IAAI,OAAO,EAAG;AACnC,UAAM,QAAQ,UAAU,SAAS,MAAM;AACvC,UAAM,OAAO,UAAU,oBAAoB,OAAO,KAAK,EAAE;AAAA,MACvD,CAAC,SAAS,KAAK,UAAU;AAAA,IAAA;AAE3B,QAAI,KAAK,WAAW,EAAG;AACvB,SAAK,gBAAgB,OAAO,KAAK,EAAE;AAAA,MACjC,CAAC,WAAW;AACV,YAAI,YAAY,CAAC,OAAQ;AACzB,cAAM,SAAS,KAAK,KAAK,CAAC,SAAS;AACjC,gBAAM,IAAI,OAAO,IAAI,KAAK,IAAI;AAC9B,iBAAO,CAAC,CAAC,KAAK,EAAE,YAAY,EAAE,YAAY;AAAA,QAC5C,CAAC;AACD,YAAI,CAAC,OAAQ;AACb,wBAAgB,IAAI,OAAO;AAC3B,2BAAA;AAAA,MACF;AAAA,MACA,MAAM;AAAA,MAAC;AAAA,IAAA;AAAA,EAEX;AAEA,SAAO;AAAA,IACL,MAAM,MAAM;AACV,UAAI,SAAU;AACd,YAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,iBAAW,CAAC,KAAK,MAAM,KAAK,CAAC,GAAG,OAAO,GAAG;AACxC,YAAI,OAAO,IAAI,GAAG,EAAG;AACrB,gBAAQ,IAAI,UAAU,QAAQ;AAC9B,gBAAQ,OAAO,GAAG;AAAA,MACpB;AACA,iBAAW,OAAO,QAAQ;AACxB,YAAI,QAAQ,IAAI,GAAG,EAAG;AACtB,gBAAQ,IAAI,KAAK,IAAI;AACrB,aAAK,KAAc,GAAG,EAAE;AAAA,UACtB,CAAC,WAAW;AACV,gBAAI,YAAY,QAAQ,IAAI,GAAG,MAAM,KAAM;AAC3C,oBAAQ,IAAI,KAAK,MAAM;AACvB,mBAAO,GAAG,UAAU,QAAQ;AAAA,UAC9B;AAAA,UACA,MAAM;AACJ,gBAAI,QAAQ,IAAI,GAAG,MAAM,KAAM,SAAQ,OAAO,GAAG;AAAA,UACnD;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,UAAU;AACR,iBAAW;AACX,sBAAgB,MAAA;AAChB,iBAAW,CAAA,EAAG,MAAM,KAAK,QAAS,SAAQ,IAAI,UAAU,QAAQ;AAChE,cAAQ,MAAA;AACR,kBAAA;AAAA,IACF;AAAA,EAAA;AAGF,WAAS,qBAA2B;AAClC,QAAI,YAAY,CAAC,cAAc,gBAAgB,SAAS,EAAG;AAC3D,QAAI,CAAC,mBAAmB;AACtB,sBAAgB,qBAAqB;AAAA,QACnC,CAAC,WAAW;AACV,cAAI,SAAU;AACd,8BAAoB;AACpB,cAAI,OAAQ,oBAAA;AAAA,QACd;AAAA,QACA,CAAC,QAAQ;AACP,wBAAc;AACd,kBAAQ,KAAK,iDAAiD,GAAG;AAAA,QACnE;AAAA,MAAA;AAEF;AAAA,IACF;AACA,UAAM,MAAM;AACZ,UAAM,WAAW,CAAC,GAAG,eAAe;AACpC,oBAAgB,MAAA;AAChB,eAAW,MAAM,SAAU,kBAAiB,IAAI,EAAE;AAClD,UAAM,WAAW,kBAAkB,IAAA,GAAO,UAAU,CAAA;AACpD,UAAM,UAAU,SAAS,OAAO,CAAC,OAAO,SAAS,EAAE,MAAM,GAAG;AAC5D,QAAI,QAAQ,WAAW,EAAG;AAC1B,sBAAkB,OAAO,CAAC,MAAM;AAC9B,iBAAW,MAAM,QAAS,GAAE,OAAO,EAAE,IAAI;AAAA,IAC3C,CAAC;AAAA,EACH;AACF;AAOA,eAAsB,uBACpB,MACA,iBACyC;AACzC,QAAM,cAAc,gBAAgB,IAAA,GAAO;AAC3C,MAAI,eAAe,oBAAoB,WAAW,GAAG;AACnD,WAAO,KAAK,KAA0B,WAAW;AAAA,EACnD;AAEA,QAAM,cAAc,KAAK,OAA4B;AAAA,IACnD,cAAc,EAAE,MAAM,oBAAA;AAAA,IACtB,QAAQ,CAAA;AAAA,EAAC,CACV;AACD,kBAAgB,OAAO,CAAC,MAAM;AAC5B,QAAI,CAAC,EAAE,oBAAqB,GAAE,sBAAsB,YAAY;AAAA,EAClE,CAAC;AAGD,QAAM,UAAU,gBAAgB,IAAA,GAAO;AACvC,MAAI,WAAW,YAAY,YAAY,OAAO,oBAAoB,OAAO,GAAG;AAC1E,WAAO,KAAK,KAA0B,OAAO;AAAA,EAC/C;AACA,SAAO;AACT;AAOA,MAAM,mBAAmB;AChKlB,SAAS,kBACd,kBACa;AACb,MAAI,sCAAsB,IAAA;AAC1B,MAAI,kBAAuC,eAAA;AAC3C,MAAI,iBAAsC;AAC1C,MAAI,WAAW;AAEf,mBAAiB,GAAG,UAAU,kBAAkB;AAChD,SAAO,iBAAiB,cAAc,YAAY;AAElD,SAAO;AAAA,IACL,sBAAsB,WAAW;AAC/B,UAAI,SAAU;AACd,wBAAkB,IAAI,IAAI,SAAS;AACnC,yBAAA;AAAA,IACF;AAAA,IACA,UAAU;AACR,iBAAW;AACX,uBAAiB,IAAI,UAAU,kBAAkB;AACjD,aAAO,oBAAoB,cAAc,YAAY;AAAA,IACvD;AAAA,EAAA;AAGF,WAAS,eAAqB;AAC5B,QAAI,SAAU;AACd,UAAM,WAAW,eAAA;AACjB,UAAM,WAAW,iBAAiB,IAAA,GAAO,cAAc;AACvD,QAAI,aAAa,UAAU;AACzB,wBAAkB;AAClB;AAAA,IACF;AACA,QAAI,aAAa,MAAM;AACrB,wBAAkB;AAClB,kBAAY,IAAI;AAChB;AAAA,IACF;AACA,QAAI,gBAAgB,IAAI,QAAQ,GAAG;AACjC,wBAAkB;AAClB,kBAAY,QAAQ;AACpB;AAAA,IACF;AACA,sBAAkB;AAAA,EACpB;AAEA,WAAS,qBAA2B;AAClC,QAAI,mBAAmB,gBAAgB,IAAI,eAAe,GAAG;AAC3D,YAAM,SAAS;AACf,wBAAkB;AAClB,kBAAY,MAAM;AAClB;AAAA,IACF;AACA,UAAM,WAAW,iBAAiB,IAAA,GAAO,cAAc;AACvD,QAAI,YAAY,CAAC,gBAAgB,IAAI,QAAQ,eAAe,IAAI;AAAA,EAClE;AAEA,WAAS,YAAY,UAAqC;AACxD,QAAI,iBAAiB,OAAO,eAAe,SAAU;AACrD,qBAAiB,OAAO,CAAC,MAAM;AAC7B,QAAE,aAAa;AAAA,IACjB,CAAC;AAAA,EACH;AAEA,WAAS,qBAA2B;AAClC,QAAI,SAAU;AACd,UAAM,WAAW,iBAAiB,IAAA,GAAO,cAAc;AACvD,QAAI,aAAa,eAAgB;AACjC,qBAAiB;AACjB,QAAI,mBAAmB,aAAa,gBAAiB,mBAAkB;AACvE,oBAAgB,QAAQ;AAAA,EAC1B;AACF;AAOA,MAAM,cAAc;AACpB,MAAM,gBAAgB,oBAAI,IAAI,CAAC,OAAO,WAAW,CAAC;AAElD,SAAS,iBAAsC;AAC7C,QAAM,MAAM,IAAI,gBAAgB,OAAO,SAAS,KAAK,MAAM,CAAC,CAAC,EAAE;AAAA,IAC7D;AAAA,EAAA;AAEF,SAAO,OAAO,oBAAoB,GAAG,IAAI,MAAM;AACjD;AAEA,SAAS,gBAAgB,UAAqC;AAC5D,QAAM,SAAS,IAAI,gBAAgB,OAAO,SAAS,KAAK,MAAM,CAAC,CAAC;AAChE,OAAK,OAAO,IAAI,WAAW,KAAK,UAAU,SAAU;AACpD,MAAI,SAAU,QAAO,IAAI,aAAa,QAAQ;AAAA,MACzC,QAAO,OAAO,WAAW;AAC9B,MAAI;AACF,YAAQ,aAAa,MAAM,IAAI,MAAM,cAAc,MAAM,CAAC;AAAA,EAC5D,QAAQ;AAAA,EAER;AACF;AAEA,SAAS,cAAc,QAAiC;AACtD,QAAM,QAAkB,CAAA;AACxB,SAAO,QAAQ,CAAC,OAAO,QAAQ;AAC7B,QAAI,CAAC,MAAO;AACZ,UAAM;AAAA,MACJ,GAAG,GAAG,IAAI,cAAc,IAAI,GAAG,IAAI,QAAQ,mBAAmB,KAAK,CAAC;AAAA,IAAA;AAAA,EAExE,CAAC;AACD,SAAO,MAAM,KAAK,GAAG;AACvB;AClFA,MAAM,oBAAoB;AAC1B,MAAM,uBAAuB;AAC7B,MAAM,sBAAsB;AAC5B,MAAM,oBAAoB;AAE1B,MAAM,eAAe;AAIrB,MAAM,cAAwB,YAAY,EAAE;AAM5C,MAAM,mCAAmB,IAAA;AAEzB,SAAS,YACP,MACA,QAC4B;AAC5B,MAAI,SAAS,aAAa,IAAI,MAAM;AACpC,MAAI,CAAC,QAAQ;AACX,aAAS,KAAK,OAAwB,EAAE,YAAY,MAAM;AAC1D,iBAAa,IAAI,QAAQ,MAAM;AAC/B,WAAO;AAAA,EACT;AACA,QAAM,MAAM,OAAO,IAAA;AACnB,MAAI,KAAK,cAAc,QAAQ,KAAK,MAAM,MAAM;AAC9C,WAAO,OAAO,CAAC,MAAM;AACnB,QAAE,aAAa;AACf,QAAE,KAAK;AAAA,IACT,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAwBO,MAAM,qBAAqB,CAAC,YAAyB;AAC1D,QAAM,SAAS,QAAQ,aAAa,YAAY;AAChD,MAAI,CAAC,UAAU,CAAC,oBAAoB,MAAM,GAAG;AAC3C,YAAQ;AAAA,MACN,2FACwB,YAAY,mBAAmB,KAAK,UAAU,MAAM,CAAC;AAAA,IAAA;AAE/E,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,QAAM,SAAuB;AAE7B,QAAM,YAAY,UAAU,SAAS,OAAO,OAAO;AACnD,MAAI,CAAC,WAAW;AACd,YAAQ;AAAA,MACN;AAAA,IAAA;AAEF,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,QAAM,OAAa;AAEnB,MAAI,gBAA6C;AACjD,QAAM,oCAAoB,IAAA;AAI1B,MAAI,kBAA8C;AAElD,QAAM,mBAAmB,YAAY,MAAM,MAAM;AACjD,QAAM,cAAc,kBAAkB,gBAAgB;AAMtD,QAAM,0CAA0B,IAAA;AAIhC,QAAM,oCAAoB,IAAA;AAI1B,QAAM,sCAAsB,IAAA;AAC5B,MAAI,mBAAmC,CAAA;AACvC,MAAI,YAAuB;AAAA,IACzB,MAAM;AAAA,MACJ,KAAK;AAAA,MACL,QAAQ;AAAA,MACR,SAAS,CAAA;AAAA,MACT,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,mBAAmB;AAAA,IAAA;AAAA,IAErB,QAAQ,CAAA;AAAA,IACR,qBAAqB;AAAA,EAAA;AAIvB,QAAM,gBAAgB,oBAAoB,SAAS,MAAM,iBAAiB;AAE1E,QAAM,gBAAgB,oBAAoB,IAAI;AAK9C,QAAM,kCAAkB,IAAA;AAIxB,QAAM,mCAAmB,IAAA;AAEzB,MAAI,WAAW;AACf,MAAI,iBAAiB;AACrB,MAAI,gBAAgB;AACpB,MAAI,iBAAuD;AAC3D,MAAI,iBAA6D;AACjE,MAAI,mBACF;AAGF,QAAM,kBAAkB,MAAM;AAC5B,mBAAA;AACA,cAAA;AAAA,EACF;AACA,QAAM,kBAAkB,MAAM;AAC5B,UAAM,MAAM,eAAe,IAAA,IAAQ,YAAY,GAAG,gBAAgB;AAClE,QAAI,SAAS,iBAAiB,OAAO,MAAO,gBAAA;AAAA,EAC9C;AAGA,QAAM,qBAAqB,MAAM;AAC/B,oBAAA;AACA,cAAA;AAAA,EACF;AAEA,QAAM,YAAY,CAAC,UAAwB;AACzC,UAAM,SAAS,MAAM;AACrB,QAAI,EAAE,SAAS,QAAS;AACxB,UAAM,MAAM,aAAa,OAAO,GAAG;AACnC,gBAAY,IAAI,MAAM,YAAY,IAAI,GAAG,KAAK,KAAK,CAAC;AACpD,sBAAkB,GAAG;AACrB,sBAAA;AACA,cAAA;AAAA,EACF;AAEA,QAAM,cAAc,CAAC,UAA0B;AAC7C,UAAM,SAAS,MAAM;AACrB,QAAI,EAAE,SAAS,QAAS;AACxB,UAAM,MAAM,aAAa,OAAO,GAAG;AACnC,UAAM,QAAQ,YAAY,IAAI,GAAG,KAAK;AACtC,QAAI,SAAS,EAAG,aAAY,OAAO,GAAG;AAAA,QACjC,aAAY,IAAI,KAAK,QAAQ,CAAC;AACnC,cAAA;AAAA,EACF;AAEA,QAAM,SAAwB,YAAY;AACxC,UAAM,SAAS,MAAM,KAAK,KAAgB,MAAM;AAChD,QAAI,SAAU;AACd,oBAAgB;AAChB,WAAO,GAAG,UAAU,eAAe;AACnC,mBAAA;AACA,cAAA;AAAA,EACF,GAAA;AACA,QAAM,MAAM,CAAC,QAAQ;AACnB,YAAQ,MAAM,uDAAuD,GAAG;AAAA,EAC1E,CAAC;AAED,mBAAiB,GAAG,UAAU,kBAAkB;AAEhD,QAAM,cAAc,CAAC,UAA0B;AAC7C,UAAM,EAAE,KAAA,IAAS,MAAM,OAAO;AAE9B,QAAI,SAAS,mBAAmB;AAC9B,aAAqB,OAAO,CAAC,YAAY;AACvC,gBAAQ,MAAM;AAAA,MAChB,CAAC;AACD;AAAA,IACF;AAEA,QAAI,SAAS,sBAAsB;AACjC,aAAqB,OAAO,CAAC,YAAY;AACvC,gBAAQ,iBAAiB,GAAG;AAAA,MAC9B,CAAC;AACD;AAAA,IACF;AAEA,QAAI,SAAS,qBAAqB;AAIhC,aAAkB,OAAO,CAAC,YAAY;AACpC,gBAAQ,SAAS;AACjB,wBAAgB,IAAI,OAAO;AAC3B,YAAI,gBAAgB,SAAS,EAAG,UAAA;AAChC,eAAO,MAAM;AACX,0BAAgB,OAAO,OAAO;AAC9B,cAAI,gBAAgB,SAAS,EAAG,YAAA;AAAA,QAClC;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAEA,QAAI,SAAS,mBAAmB;AAI9B,YAAM,YAAa,MAAM,OAAO,SAA+B;AAC/D,UAAI,OAAO,cAAc,YAAY,CAAC,oBAAoB,SAAS,GAAG;AACpE;AAAA,MACF;AACA,YAAM,SAAS,aAAa,SAAS;AACrC,aAAiB,OAAO,CAAC,YAAY;AACnC,cAAM,WAAW,gBAAgB,MAAM;AACvC,sBAAc,IAAI,QAAQ,SAAS,KAAK;AACxC,gBAAQ,QAAQ;AAChB,YAAI,MAAM,oBAAoB,IAAI,MAAM;AACxC,YAAI,CAAC,IAAK,qBAAoB,IAAI,QAAS,MAAM,oBAAI,KAAM;AAC3D,YAAI,IAAI,OAAO;AACf,eAAO,MAAM;AACX,cAAK,OAAO,OAAO;AACnB,cAAI,IAAK,SAAS,GAAG;AACnB,gCAAoB,OAAO,MAAM;AACjC,0BAAc,OAAO,MAAM;AAAA,UAC7B;AAAA,QACF;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,iBAAiB,uBAAuB,WAAW;AAC3D,UAAQ,iBAAiB,qBAAqB,SAAS;AACvD,UAAQ,iBAAiB,uBAAuB,WAAW;AAE3D,SAAO,MAAM;AACX,eAAW;AACX,QAAI,mBAAmB,KAAM,cAAa,cAAc;AACxD,kBAAc,QAAA;AACd,kBAAc,QAAA;AACd,gBAAY,QAAA;AACZ,YAAQ,oBAAoB,uBAAuB,WAAW;AAC9D,YAAQ,oBAAoB,qBAAqB,SAAS;AAC1D,YAAQ,oBAAoB,uBAAuB,WAAW;AAC9D,mBAAe,IAAI,UAAU,eAAe;AAC5C,qBAAiB,IAAI,UAAU,eAAe;AAC9C,eAAW,CAAA,EAAG,CAAC,KAAK,cAAe,GAAE,IAAI,UAAU,eAAe;AAClE,qBAAiB,IAAI,UAAU,kBAAkB;AACjD,sBAAkB;AAClB,kBAAc,MAAA;AACd,oBAAgB,MAAA;AAChB,wBAAoB,MAAA;AACpB,kBAAc,MAAA;AACd,gBAAY,MAAA;AACZ,iBAAa,MAAA;AACb,oBAAgB;AAAA,EAClB;AAKA,WAAS,WAAiB;AACxB,uBAAA,EAAqB;AAAA,MACnB,MAAM;AACJ,0BAAA;AACA,kBAAA;AAAA,MACF;AAAA,MACA,CAAC,QAAQ,QAAQ,KAAK,yCAAyC,GAAG;AAAA,IAAA;AAEpE,cAAA;AAAA,EACF;AAEA,WAAS,aAAmB;AAC1B,kBAAc,aAAa,EAAE;AAAA,EAC/B;AAOA,WAAS,qBAA0D;AACjE,QAAI,eAAgB,QAAO;AAC3B,UAAM,WAAW,YAAY;AAC3B,YAAM;AACN,UAAI,YAAY,CAAC,cAAe,QAAO;AACvC,YAAM,WAAW,cAAc,IAAA,IAAQ,YAAY,GAAG;AACtD,UAAI,YAAY,QAAQ,kBAAkB,IAAI,QAAQ,EAAG,QAAO;AAChE,YAAM,YAAY,MAAM,gBAAgB,MAAM,aAAa;AAC3D,UAAI,SAAU,QAAO;AACrB,qBAAA;AACA,aAAO;AAAA,IACT,GAAA;AACA,qBAAiB;AACjB,YAAQ,MAAM,MAAM;AAClB,UAAI,mBAAmB,QAAS,kBAAiB;AAAA,IACnD,CAAC;AACD,WAAO;AAAA,EACT;AAIA,WAAS,oBAAoE;AAC3E,QAAI,iBAAkB,QAAO;AAC7B,UAAM,UAAU,qBAAqB;AAAA,MAAK,CAAC,cACzC,aAAa,CAAC,WAAW,uBAAuB,MAAM,SAAS,IAAI;AAAA,IAAA;AAErE,uBAAmB;AACnB,YAAQ,MAAM,MAAM;AAClB,UAAI,qBAAqB,QAAS,oBAAmB;AAAA,IACvD,CAAC;AACD,WAAO;AAAA,EACT;AAEA,WAAS,iBAAuB;AAC9B,QAAI,SAAU;AACd,QAAI,CAAC,cAAe;AACpB,QAAI,gBAAgB;AAClB,sBAAgB;AAChB;AAAA,IACF;AACA,qBAAiB;AACjB,UAAM,YAAY;AAChB,UAAI;AAGF,cAAM,YAAY,MAAM,uBAAA;AACxB,YAAI,SAAU;AACd,cAAM,SAAS,WAAW,IAAA,GAAO,UAAU,CAAA,GAAI;AAAA,UAC7C;AAAA,QAAA;AAEF,cAAM,YAAY,MAAM;AAAA,UACtB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,MAAM;AAAA,QAAA;AAER,YAAI,SAAU;AACd,cAAM,YAAY,IAAI,IAAI,SAAS;AACnC,mBAAW,CAAC,KAAK,MAAM,KAAK,eAAe;AACzC,cAAI,UAAU,IAAI,GAAG,EAAG;AACxB,iBAAO,IAAI,UAAU,eAAe;AACpC,wBAAc,OAAO,GAAG;AAAA,QAC1B;AACA,2BAAmB;AACnB,oBAAY,sBAAsB,SAAS;AAAA,MAC7C,SAAS,KAAK;AACZ,gBAAQ,MAAM,2BAA2B,GAAG;AAAA,MAC9C,UAAA;AACE,yBAAiB;AAGjB,kBAAA;AACA,YAAI,eAAe;AACjB,0BAAgB;AAChB,yBAAA;AAAA,QACF;AAAA,MACF;AAAA,IACF,GAAA;AAAA,EACF;AAMA,WAAS,YAAkB;AACzB,QAAI,SAAU;AACd,kBAAc,MAAM,iBAAiB;AACrC,QAAI,gBAAgB,OAAO,EAAG,sBAAA;AAC9B,UAAM,OAAO,YAAA;AACb,QAAI,gBAAgB,WAAW,IAAI,EAAG;AACtC,gBAAY;AACZ,eAAW,WAAW,gBAAiB,SAAQ,IAAI;AAAA,EACrD;AAIA,WAAS,kBAAkC;AACzC,UAAM,OAAO,CAAC,GAAG,YAAY,KAAA,CAAM,EAAE;AAAA,MACnC,CAAC,QAAQ,aAAa,IAAI,GAAG,MAAM;AAAA,IAAA;AAErC,UAAM,WAAW,iBAAiB,IAAA,GAAO,cAAc;AACvD,UAAM,SAAS,WACX,cAAc,IAAI,QAAQ,GAAG,OAAO,SACpC;AACJ,eAAW,SAAS,OAAO,OAAO,UAAU,CAAA,CAAE,GAAG;AAC/C,WAAK,KAAK,aAAa,MAAM,QAAQ,CAAC;AAAA,IACxC;AACA,WAAO;AAAA,EACT;AAKA,WAAS,uBAA6B;AACpC,QAAI,SAAU;AACd,UAAM,QAAgC,CAAA;AACtC,QAAI,iBAAiB;AACnB,YAAM,KAAK;AAAA,QACT,aAAa;AAAA,QACb,SAAS,gBAAgB,gBAAgB,OAAO,UAAU,CAAA,CAAE;AAAA,QAC5D,YAAY;AAAA,MAAA,CACb;AAAA,IACH;AACA,UAAM,WAAW,iBAAiB,IAAA,GAAO,cAAc;AACvD,UAAM,UACJ,YAAY,iBAAiB,SAAS,QAAQ,IAC1C,CAAC,UAAU,GAAG,iBAAiB,OAAO,CAAC,MAAM,MAAM,QAAQ,CAAC,IAC5D;AACN,eAAW,OAAO,SAAS;AACzB,YAAM,SAAS,cAAc,IAAI,GAAG;AACpC,YAAM,MAAM,QAAQ,IAAA;AACpB,UAAI,CAAC,UAAU,CAAC,OAAO,IAAI,aAAa,OAAW;AACnD,YAAM,KAAK;AAAA,QACT,aAAa;AAAA,QACb,SAAS,gBAAgB,IAAI,MAAM;AAAA,QACnC,YAAY;AAAA,MAAA,CACb;AAAA,IACH;AACA,kBAAc,aAAa,KAAK;AAAA,EAClC;AAIA,WAAS,cAAyB;AAChC,UAAM,SAAyB,CAAA;AAC/B,eAAW,OAAO,kBAAkB;AAClC,YAAM,MAAM,cAAc,IAAI,GAAG,GAAG,IAAA;AACpC,UAAI,CAAC,OAAO,IAAI,aAAa,OAAW;AACxC,aAAO,KAAK;AAAA,QACV;AAAA,QACA,QAAQ,gBAAgB,IAAI,MAAM;AAAA,QAClC,SAAS,gBAAgB,IAAI,MAAM;AAAA,QACnC,YAAY,IAAI,OAAO;AAAA,QACvB,MAAM,IAAI,QAAQ;AAAA,QAClB,mBAAmB,IAAI,qBAAqB;AAAA,MAAA,CAC7C;AAAA,IACH;AACA,WAAO;AAAA,MACL,MAAM,mBAAA;AAAA,MACN;AAAA,MACA,qBACE,iBAAiB,IAAA,GAAO,uBAAuB;AAAA,IAAA;AAAA,EAErD;AAOA,WAAS,gBACP,WACqB;AACrB,UAAM,2BAAW,IAAA;AACjB,QAAI,SAAS,aAAa;AAC1B,WAAO,UAAU,CAAC,KAAK,IAAI,MAAM,GAAG;AAClC,WAAK,IAAI,MAAM;AACf,YAAM,MAAM,cAAc,IAAI,MAAM,GAAG,IAAA;AACvC,UAAI,CAAC,OAAO,IAAI,aAAa,OAAW,QAAO;AAC/C,eAAS,IAAI,UAAU;AAAA,IACzB;AACA,WAAO;AAAA,EACT;AAMA,WAAS,qBAAmC;AAC1C,UAAM,MAAM,iBAAiB,OAAO;AACpC,UAAM,aAAa,iBAAiB,IAAA,GAAO,OAAO,UAAU;AAC5D,UAAM,OAAO,iBAAiB,IAAA,GAAO,QAAQ;AAC7C,UAAM,oBACJ,iBAAiB,IAAA,GAAO,qBAAqB;AAE/C,UAAM,aAAa,iBAAiB,IAAA,GAAO;AAC3C,QAAI,cAAc,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACpD,aAAO;AAAA,QACL;AAAA,QACA,QAAQ;AAAA,QACR,SAAS,gBAAgB,UAAU;AAAA,QACnC;AAAA,QACA;AAAA,QACA;AAAA,MAAA;AAAA,IAEJ;AAEA,UAAM,UAAU,CAAC,GAAG,YAAY,KAAA,CAAM,EACnC,OAAO,CAAC,MAAM,aAAa,IAAI,CAAC,MAAM,IAAI,EAC1C,IAAI,CAAC,OAAO,EAAE,KAAK,GAAG,UAAU,MAAM,UAAU,KAAA,EAAO,EACvD,KAAK,WAAW;AACnB,WAAO,EAAE,KAAK,QAAQ,MAAM,SAAS,YAAY,MAAM,kBAAA;AAAA,EACzD;AAOA,WAAS,gBAAgB,QAAgC;AACvD,UAAM,QAAQ,iBAAiB,IAAA,GAAO,KAAK,MAAM;AACjD,WAAO,EAAE,OAAO,OAAO,QAAQ,KAAA;AAAA,EACjC;AAIA,WAAS,kBAAwB;AAC/B,eAAW,CAAC,QAAQ,GAAG,KAAK,qBAAqB;AAC/C,YAAM,WAAW,gBAAgB,MAAM;AACvC,UAAI,UAAU,SAAS,OAAO,cAAc,IAAI,MAAM,KAAK,IAAI,GAAG;AAChE;AAAA,MACF;AACA,oBAAc,IAAI,QAAQ,SAAS,KAAK;AACxC,iBAAW,WAAW,CAAC,GAAG,GAAG,WAAW,QAAQ;AAAA,IAClD;AAAA,EACF;AAKA,WAAS,kBAAkB,KAAyB;AAClD,QAAI,aAAa,IAAI,GAAG,EAAG;AAC3B,UAAM,YAAY;AAChB,UAAI;AACF,cAAM,SAAS,MAAM,KAAK,KAAgB,GAAG;AAC7C,YAAI,SAAU;AACd,cAAM,OAAO,OAAO,IAAA,IAAQ,YAAY,GAAG;AAC3C,cAAM,UAAU,QAAQ,QAAQ,kBAAkB,IAAI,IAAI;AAC1D,YAAI,aAAa,IAAI,GAAG,MAAM,QAAS;AACvC,qBAAa,IAAI,KAAK,OAAO;AAE7B,0BAAA;AACA,kBAAA;AAAA,MACF,QAAQ;AAAA,MAER;AAAA,IACF,GAAA;AAAA,EACF;AAMA,iBAAe,yBAA8D;AAC3E,QAAI,CAAC,cAAe,QAAO;AAC3B,UAAM,eAAe,cAAc,IAAA,IAAQ,YAAY,GAAG;AAC1D,QAAI,CAAC,gBAAgB,CAAC,oBAAoB,YAAY,EAAG,QAAO;AAChE,QAAI,mBAAmB,gBAAgB,QAAQ,cAAc;AAC3D,aAAO;AAAA,IACT;AACA,QAAI,gBAAiB,iBAAgB,IAAI,UAAU,eAAe;AAClE,UAAM,SAAS,MAAM,KAAK,KAAe,YAAY;AACrD,QAAI,SAAU,QAAO;AACrB,sBAAkB;AAClB,WAAO,GAAG,UAAU,eAAe;AACnC,wBAAA;AACA,WAAO;AAAA,EACT;AAIA,WAAS,oBAA0B;AACjC,QAAI,mBAAmB,KAAM;AAC7B,qBAAiB,WAAW,MAAM;AAChC,uBAAiB;AACjB,0BAAA;AAAA,IACF,GAAG,CAAC;AAAA,EACN;AAQA,WAAS,sBAA4B;AACnC,QAAI,YAAY,CAAC,mBAAmB,gBAAgB,SAAS,EAAG;AAChE,UAAM,WAAW,gBAAgB,IAAA,GAAO,UAAU,CAAA;AAClD,UAAM,QAAQ,CAAC,GAAG,YAAY,KAAA,CAAM,EAAE;AAAA,MACpC,CAAC,QAAQ,aAAa,IAAI,GAAG,MAAM,SAAS,CAAC,SAAS,GAAG;AAAA,IAAA;AAE3D,QAAI,MAAM,WAAW,EAAG;AACxB,oBAAgB,OAAO,CAAC,MAAM;AAC5B,iBAAW,OAAO,OAAO;AACvB,YAAI,CAAC,EAAE,OAAO,GAAG,GAAG;AAClB,YAAE,OAAO,GAAG,IAAI,EAAE,UAAU,KAAK,UAAU,YAAA;AAAA,QAC7C;AAAA,MACF;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAIA,eAAe,iBACb,MACA,OACA,SACA,aACA,YACyB;AACzB,QAAM,8BAAc,IAAA;AACpB,QAAM,QAAwB,CAAA;AAC9B,QAAM,QAAwB,CAAC,GAAG,KAAK;AACvC,SAAO,MAAM,QAAQ;AACnB,UAAM,MAAM,MAAM,MAAA;AAClB,QAAI,QAAQ,IAAI,GAAG,EAAG;AACtB,YAAQ,IAAI,GAAG;AACf,UAAM,KAAK,GAAG;AAEd,QAAI,IAAI,QAAQ,IAAI,GAAG;AACvB,QAAI,CAAC,GAAG;AACN,UAAI,MAAM,KAAK,KAAe,GAAG;AACjC,UAAI,WAAA,EAAc,QAAO;AACzB,cAAQ,IAAI,KAAK,CAAC;AAClB,QAAE,GAAG,UAAU,WAAW;AAAA,IAC5B;AACA,UAAM,UAAU,EAAE,IAAA,GAAO,UAAU,CAAA,GAAI,OAAO,mBAAmB;AACjE,UAAM,QAAQ,GAAG,MAAM;AAAA,EACzB;AACA,SAAO;AACT;AAIA,SAAS,gBACP,QACkB;AAClB,SAAO,OAAO,QAAQ,MAAM,EACzB,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;AAAA,IACtB;AAAA,IACA,UAAU,MAAM;AAAA,IAChB,UAAU,MAAM;AAAA,EAAA,EAChB,EACD,KAAK,WAAW;AACrB;AAEA,SAAS,gBAAgB,GAAc,GAAuB;AAC5D,MAAI,EAAE,wBAAwB,EAAE,oBAAqB,QAAO;AAC5D,MAAI,CAAC,eAAe,EAAE,MAAM,EAAE,IAAI,EAAG,QAAO;AAC5C,MAAI,EAAE,OAAO,WAAW,EAAE,OAAO,OAAQ,QAAO;AAChD,WAAS,IAAI,GAAG,IAAI,EAAE,OAAO,QAAQ,KAAK;AACxC,QAAI,CAAC,eAAe,EAAE,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,EAAG,QAAO;AAAA,EACxD;AACA,SAAO;AACT;AAEA,SAAS,eAAe,GAAiB,GAA0B;AACjE,SACE,EAAE,QAAQ,EAAE,OACZ,EAAE,WAAW,EAAE,UACf,EAAE,eAAe,EAAE,cACnB,EAAE,SAAS,EAAE,QACb,EAAE,sBAAsB,EAAE,qBAC1B,iBAAiB,EAAE,SAAS,EAAE,OAAO;AAEzC;AAEA,SAAS,YAAY,GAAmB,GAA2B;AACjE,SAAO,EAAE,MAAM,EAAE,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,IAAI;AAClD;AAEA,SAAS,iBAAiB,GAAqB,GAA8B;AAC3E,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,QAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAK,QAAO;AAClC,QAAI,EAAE,CAAC,EAAE,aAAa,EAAE,CAAC,EAAE,SAAU,QAAO;AAC5C,QAAI,CAAC,UAAU,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE,QAAQ,EAAG,QAAO;AAAA,EACvD;AACA,SAAO;AACT;AAEA,SAAS,UAAU,GAAoB,GAA6B;AAClE,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,CAAC,KAAK,CAAC,KAAK,EAAE,WAAW,EAAE,OAAQ,QAAO;AAC9C,QAAM,MAAM,IAAI,IAAI,CAAC;AACrB,SAAO,EAAE,MAAM,CAAC,MAAM,IAAI,IAAI,CAAC,CAAC;AAClC;"}
@@ -4,7 +4,6 @@ const CHANGE_GROUP_DOC_VERSION = 1;
4
4
  const INACTIVITY_GAP_MS = 10 * 60 * 1e3;
5
5
  const SLICE_BUDGET_MS = 8;
6
6
  const GROUPING_DEBOUNCE_MS = 250;
7
- const LOCAL_PATCH_SOURCES = /* @__PURE__ */ new Set(["change", "changeAt", "emptyChange"]);
8
7
  async function ensureChangeGroupDoc(repo, draftHandle) {
9
8
  const draft = draftHandle.doc();
10
9
  const existingUrl = getChangeGroupDocUrl(draft);
@@ -74,6 +73,48 @@ function computeRangeEditCounts(doc, from, to) {
74
73
  return { additions: 0, deletions: 0 };
75
74
  }
76
75
  }
76
+ async function editCountsSince(doc, since, tick = async () => true) {
77
+ const skipObjs = /* @__PURE__ */ new Set();
78
+ objectIdsUnder(doc["@patchwork"], skipObjs);
79
+ const counts = /* @__PURE__ */ new Map();
80
+ for (const change of Automerge.getChangesSince(doc, since)) {
81
+ if (!await tick()) return null;
82
+ const decoded = Automerge.decodeChange(change);
83
+ counts.set(decoded.hash, countOps(decoded, skipObjs));
84
+ }
85
+ return counts;
86
+ }
87
+ function objectIdsUnder(value, out) {
88
+ if (!value || typeof value !== "object") return;
89
+ const id = Automerge.getObjectId(value);
90
+ if (id) out.add(id);
91
+ if (Array.isArray(value)) {
92
+ for (const item of value) objectIdsUnder(item, out);
93
+ return;
94
+ }
95
+ for (const [key, child] of Object.entries(value)) {
96
+ if (typeof child === "string") {
97
+ const textId = Automerge.getObjectId(value, key);
98
+ if (typeof textId === "string") out.add(textId);
99
+ } else {
100
+ objectIdsUnder(child, out);
101
+ }
102
+ }
103
+ }
104
+ function countOps(change, skipObjs) {
105
+ let additions = 0;
106
+ let deletions = 0;
107
+ change.ops.forEach((op, i) => {
108
+ const skipped = skipObjs.has(op.obj) || op.obj === "_root" && op.key === "@patchwork";
109
+ if (op.action.startsWith("make") && skipped) {
110
+ skipObjs.add(`${change.startOp + i}@${change.actor}`);
111
+ }
112
+ if (skipped || op.action === "markEnd") return;
113
+ if (op.action === "del") deletions += 1;
114
+ else additions += 1;
115
+ });
116
+ return { additions, deletions };
117
+ }
77
118
  function countPatches(patches) {
78
119
  let additions = 0;
79
120
  let deletions = 0;
@@ -93,17 +134,22 @@ function countPatches(patches) {
93
134
  }
94
135
  async function getDocCreationTime(repo, url) {
95
136
  if (!url) return void 0;
137
+ const cached = creationTimes.get(url);
138
+ if (cached !== void 0) return cached;
96
139
  try {
97
140
  const handle = await repo.find(url);
98
141
  const doc = handle.doc();
99
142
  if (!doc) return void 0;
100
143
  const metas = Automerge.getChangesMetaSince(doc, []);
101
- return metas[0]?.time || void 0;
144
+ const time = metas[0]?.time || void 0;
145
+ if (time !== void 0) creationTimes.set(url, time);
146
+ return time;
102
147
  } catch (err) {
103
148
  console.warn("[drafts] failed to resolve creation time for:", url, err);
104
149
  return void 0;
105
150
  }
106
151
  }
152
+ const creationTimes = /* @__PURE__ */ new Map();
107
153
  function newestFirst(a, b) {
108
154
  return b.time - a.time || b.seq - a.seq;
109
155
  }
@@ -126,30 +172,33 @@ function splitIntoGroups(rowsNewestFirst) {
126
172
  function groupId(rowsNewestFirst) {
127
173
  return `tg-${rowsNewestFirst[0].hash}`;
128
174
  }
129
- function collectMemberRows(out, member, doc, since, createdAt) {
175
+ async function collectMemberRows(out, member, doc, since, createdAt, slicer) {
130
176
  let metas;
177
+ let counts;
131
178
  try {
132
179
  metas = Automerge.getChangesMetaSince(doc, since);
180
+ counts = await editCountsSince(doc, since, slicer.tick);
133
181
  } catch (err) {
134
182
  console.warn(
135
183
  "[drafts] change grouping: failed to read changes for member:",
136
184
  member.url,
137
185
  err
138
186
  );
139
- return;
187
+ return true;
140
188
  }
189
+ if (!counts) return false;
141
190
  metas.forEach((meta, seq) => {
142
191
  if (createdAt !== void 0 && meta.time && meta.time < createdAt) return;
143
192
  out.push({
144
193
  memberUrl: member.url,
145
- doc,
146
194
  hash: meta.hash,
147
- deps: meta.deps,
148
195
  time: meta.time,
149
196
  actor: meta.actor,
150
- seq
197
+ seq,
198
+ ...counts.get(meta.hash) ?? { additions: 0, deletions: 0 }
151
199
  });
152
200
  });
201
+ return true;
153
202
  }
154
203
  function dedupedActors(rowsNewestFirst) {
155
204
  const actors = [];
@@ -161,34 +210,34 @@ function dedupedActors(rowsNewestFirst) {
161
210
  function idle() {
162
211
  return new Promise((resolve) => {
163
212
  if (typeof requestIdleCallback === "function") {
164
- requestIdleCallback(() => resolve());
213
+ requestIdleCallback(() => resolve(), { timeout: 200 });
165
214
  } else {
166
215
  setTimeout(resolve, 0);
167
216
  }
168
217
  });
169
218
  }
170
- function createSlicer(isAborted, onYield) {
219
+ function createSlicer(isAborted) {
171
220
  let sliceStart = performance.now();
172
221
  return {
173
222
  async tick() {
174
223
  if (isAborted()) return false;
175
224
  if (performance.now() - sliceStart < SLICE_BUDGET_MS) return true;
176
- onYield();
177
225
  await idle();
178
226
  sliceStart = performance.now();
179
227
  return !isAborted();
180
228
  }
181
229
  };
182
230
  }
183
- async function buildGroup(rowsNewestFirst, slicer) {
231
+ function sumCounts(rows) {
184
232
  let additions = 0;
185
233
  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;
234
+ for (const row of rows) {
235
+ additions += row.additions;
236
+ deletions += row.deletions;
191
237
  }
238
+ return { additions, deletions };
239
+ }
240
+ function buildGroup(rowsNewestFirst) {
192
241
  const newest = rowsNewestFirst[0];
193
242
  const oldest = rowsNewestFirst[rowsNewestFirst.length - 1];
194
243
  return {
@@ -198,8 +247,7 @@ async function buildGroup(rowsNewestFirst, slicer) {
198
247
  newestMemberUrl: newest.memberUrl,
199
248
  newestHash: newest.hash,
200
249
  actors: dedupedActors(rowsNewestFirst),
201
- additions,
202
- deletions,
250
+ ...sumCounts(rowsNewestFirst),
203
251
  changeCount: rowsNewestFirst.length
204
252
  };
205
253
  }
@@ -212,20 +260,11 @@ function sameHeads(a, b) {
212
260
  const set = new Set(b);
213
261
  return a.every((h) => set.has(h));
214
262
  }
215
- function createChangeGrouper(repo, options = {}) {
263
+ function createChangeGrouper(repo) {
216
264
  const tasks = /* @__PURE__ */ new Map();
217
265
  const queue = [];
218
266
  let running = false;
219
267
  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
268
  function setTimelines(specs) {
230
269
  if (disposed) return;
231
270
  const keep = new Set(specs.map((s) => s.draftHandle.url));
@@ -279,10 +318,7 @@ function createChangeGrouper(repo, options = {}) {
279
318
  }
280
319
  for (const url of wanted) {
281
320
  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
- }
321
+ const onChange = () => {
286
322
  if (task.debounce) clearTimeout(task.debounce);
287
323
  task.debounce = setTimeout(() => {
288
324
  task.debounce = null;
@@ -346,7 +382,7 @@ function createChangeGrouper(repo, options = {}) {
346
382
  repo,
347
383
  spec.draftHandle
348
384
  );
349
- const createdAt = await creationTime(spec.rootDocUrl);
385
+ const createdAt = await getDocCreationTime(repo, spec.rootDocUrl);
350
386
  if (isAborted()) return;
351
387
  const members = [...spec.members].sort(byMemberUrl);
352
388
  const sources = [];
@@ -367,14 +403,18 @@ function createChangeGrouper(repo, options = {}) {
367
403
  const changeGroupDoc = changeGroupHandle.doc();
368
404
  if (!changeGroupDoc) return;
369
405
  const computedThrough = changeGroupDoc.computedThrough ?? {};
406
+ const slicer = createSlicer(isAborted);
370
407
  const frontier = {};
371
408
  const tails = [];
372
409
  for (const { member, doc } of sources) {
373
410
  frontier[member.url] = encodeHeads(Automerge.getHeads(doc));
374
411
  const consumed = computedThrough[member.url];
375
412
  const since = consumed ? decodeHeads(consumed) : member.clonedAt ? decodeHeads(member.clonedAt) : [];
376
- collectMemberRows(tails, member, doc, since, createdAt);
413
+ if (!await collectMemberRows(tails, member, doc, since, createdAt, slicer)) {
414
+ return;
415
+ }
377
416
  }
417
+ if (isAborted()) return;
378
418
  if (tails.length === 0) {
379
419
  const stale = Object.entries(frontier).filter(
380
420
  ([url, heads]) => !sameHeads(computedThrough[url], heads)
@@ -397,60 +437,42 @@ function createChangeGrouper(repo, options = {}) {
397
437
  const tailOldestMs = tails[tails.length - 1].time * 1e3;
398
438
  const fastOk = !!newestStored && tailOldestMs >= newestStored.startTime * 1e3 - INACTIVITY_GAP_MS && (!secondStored || tailOldestMs - secondStored.endTime * 1e3 > INACTIVITY_GAP_MS);
399
439
  if (fastOk) {
400
- await appendTail(
401
- changeGroupHandle,
402
- newestStored,
403
- tails,
404
- frontier,
405
- isAborted
406
- );
440
+ appendTail(changeGroupHandle, newestStored, tails, frontier);
407
441
  } else {
408
442
  await rebuildAll(
409
443
  changeGroupHandle,
410
444
  sources,
411
445
  createdAt,
412
446
  frontier,
413
- isAborted
447
+ isAborted,
448
+ slicer
414
449
  );
415
450
  }
416
451
  }
417
- async function appendTail(changeGroupHandle, newestStored, tailsNewestFirst, frontier, isAborted) {
452
+ function appendTail(changeGroupHandle, newestStored, tailsNewestFirst, frontier) {
418
453
  const tailGroups = splitIntoGroups(tailsNewestFirst);
419
454
  const oldestGroup = tailGroups[tailGroups.length - 1];
420
455
  const oldestGroupOldestMs = oldestGroup[oldestGroup.length - 1].time * 1e3;
421
456
  const attaches = oldestGroupOldestMs <= newestStored.endTime * 1e3 + INACTIVITY_GAP_MS;
422
457
  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
458
  changeGroupHandle.change((d) => {
445
- for (const group of built) d.groups[group.id] = group;
446
- if (attaches && extensionSums) {
447
- extendGroup(d, newestStored, oldestGroup, extensionSums);
459
+ for (const rows of freshGroups) {
460
+ const group = buildGroup(rows);
461
+ d.groups[group.id] = group;
448
462
  }
449
- for (const [url, heads] of Object.entries(frontier)) {
450
- d.computedThrough[url] = heads;
463
+ if (attaches) {
464
+ extendGroup(d, newestStored, oldestGroup, sumCounts(oldestGroup));
451
465
  }
466
+ advanceConsumed(d, frontier);
452
467
  });
453
468
  }
469
+ function advanceConsumed(d, frontier) {
470
+ for (const [url, heads] of Object.entries(frontier)) {
471
+ if (!sameHeads(d.computedThrough[url], heads)) {
472
+ d.computedThrough[url] = heads;
473
+ }
474
+ }
475
+ }
454
476
  function extendGroup(d, storedSnapshot, tailNewestFirst, sums) {
455
477
  const tailNewest = tailNewestFirst[0];
456
478
  const tailOldest = tailNewestFirst[tailNewestFirst.length - 1];
@@ -487,43 +509,29 @@ function createChangeGrouper(repo, options = {}) {
487
509
  if (extended.id !== storedSnapshot.id) delete d.groups[storedSnapshot.id];
488
510
  d.groups[extended.id] = extended;
489
511
  }
490
- async function rebuildAll(changeGroupHandle, sources, createdAt, frontier, isAborted) {
512
+ async function rebuildAll(changeGroupHandle, sources, createdAt, frontier, isAborted, slicer) {
491
513
  const rows = [];
492
514
  for (const { member, doc } of sources) {
493
515
  const since = member.clonedAt ? decodeHeads(member.clonedAt) : [];
494
- collectMemberRows(rows, member, doc, since, createdAt);
516
+ if (!await collectMemberRows(rows, member, doc, since, createdAt, slicer)) {
517
+ return;
518
+ }
495
519
  }
520
+ if (isAborted()) return;
496
521
  rows.sort(newestFirst);
497
522
  const groupsRows = splitIntoGroups(rows);
498
523
  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;
524
+ const stored = changeGroupHandle.doc()?.groups ?? {};
525
+ const fresh = groupsRows.filter((groupRows) => {
526
+ const existing = stored[groupId(groupRows)];
527
+ return !(existing && existing.changeCount === groupRows.length && existing.startTime === groupRows[groupRows.length - 1].time && existing.endTime === groupRows[0].time);
528
+ }).map(buildGroup);
519
529
  changeGroupHandle.change((d) => {
520
- for (const group of batch) d.groups[group.id] = group;
530
+ for (const group of fresh) d.groups[group.id] = group;
521
531
  for (const id of Object.keys(d.groups)) {
522
532
  if (!expectedIds.has(id)) delete d.groups[id];
523
533
  }
524
- for (const [url, heads] of Object.entries(frontier)) {
525
- d.computedThrough[url] = heads;
526
- }
534
+ advanceConsumed(d, frontier);
527
535
  });
528
536
  }
529
537
  }
@@ -544,11 +552,12 @@ async function ensureMainDraft(repo, docHandle) {
544
552
  return mainDraft;
545
553
  }
546
554
  export {
547
- computeRangeEditCounts as a,
548
- computeEditCounts as b,
555
+ ensureMainDraft as a,
556
+ computeRangeEditCounts as b,
549
557
  createChangeGrouper as c,
550
- ensureMainDraft as e,
558
+ computeEditCounts as d,
559
+ editCountsSince as e,
551
560
  getDocCreationTime as g,
552
561
  sameHeads as s
553
562
  };
554
- //# sourceMappingURL=draft-docs-Bi8K1y3a.js.map
563
+ //# sourceMappingURL=draft-docs-DyJ2l14d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"draft-docs-DyJ2l14d.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 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).\n// Not bumped for the switch from per-change diffs to op counting: the numbers\n// agree, and a bump would make still-deployed clients and this one wipe each\n// other's group docs in turn.\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// 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\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// The same +/- magnitude for every change since `since`, straight from each\n// change's own ops and keyed by hash: every insert / set / make / mark counts\n// as one addition, every del as one deletion (a text splice is one op per\n// character, so the totals match the patch-based count). Ops under\n// `@patchwork` are ignored: the skip set starts from the objects currently in\n// that subtree and, since changes come back in causal order, picks up objects\n// created inside it along the way.\nexport async function editCountsSince(\n doc: Automerge.Doc<unknown>,\n since: Automerge.Heads,\n tick: () => Promise<boolean> = async () => true\n): Promise<Map<string, EditCounts> | null> {\n const skipObjs = new Set<string>();\n objectIdsUnder((doc as Record<string, unknown>)[\"@patchwork\"], skipObjs);\n const counts = new Map<string, EditCounts>();\n for (const change of Automerge.getChangesSince(doc, since)) {\n if (!(await tick())) return null;\n const decoded = Automerge.decodeChange(change);\n counts.set(decoded.hash, countOps(decoded, skipObjs));\n }\n return counts;\n}\n\n// Object ids of a materialized container and everything inside it, including\n// the text objects behind string values in maps (strings inside lists carry\n// no reachable id).\nfunction objectIdsUnder(value: unknown, out: Set<string>): void {\n if (!value || typeof value !== \"object\") return;\n const id = Automerge.getObjectId(value);\n if (id) out.add(id);\n if (Array.isArray(value)) {\n for (const item of value) objectIdsUnder(item, out);\n return;\n }\n for (const [key, child] of Object.entries(value)) {\n if (typeof child === \"string\") {\n const textId = Automerge.getObjectId(value, key);\n if (typeof textId === \"string\") out.add(textId);\n } else {\n objectIdsUnder(child, out);\n }\n }\n}\n\nfunction countOps(\n change: Automerge.DecodedChange,\n skipObjs: Set<string>\n): EditCounts {\n let additions = 0;\n let deletions = 0;\n change.ops.forEach((op, i) => {\n const skipped =\n skipObjs.has(op.obj) || (op.obj === \"_root\" && op.key === \"@patchwork\");\n if (op.action.startsWith(\"make\") && skipped) {\n skipObjs.add(`${change.startOp + i}@${change.actor}`);\n }\n if (skipped || op.action === \"markEnd\") return;\n if (op.action === \"del\") deletions += 1;\n else additions += 1;\n });\n return { additions, deletions };\n}\n\nexport type EditCounts = { additions: number; deletions: number };\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. Immutable once known, so resolved times are cached per url.\nexport async function getDocCreationTime(\n repo: Repo,\n url: AutomergeUrl | undefined\n): Promise<number | undefined> {\n if (!url) return undefined;\n const cached = creationTimes.get(url);\n if (cached !== undefined) return cached;\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 const time = metas[0]?.time || undefined;\n if (time !== undefined) creationTimes.set(url, time);\n return time;\n } catch (err) {\n console.warn(\"[drafts] failed to resolve creation time for:\", url, err);\n return undefined;\n }\n}\n\nconst creationTimes = new Map<AutomergeUrl, number>();\n\n// One member change awaiting aggregation, with its edit counts already\n// resolved. `seq` is the change's index within its member's gathered metas,\n// used only to break same-second timestamp ties (meta.time is\n// second-resolution) with the doc's own causal order.\ntype PendingChange = EditCounts & {\n memberUrl: AutomergeUrl;\n hash: 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. Each change is decoded once for its\n// edit counts, in idle slices; resolves false if the run was aborted.\nasync function collectMemberRows(\n out: PendingChange[],\n member: DraftMemberDoc,\n doc: Automerge.Doc<unknown>,\n since: Automerge.Heads,\n createdAt: number | undefined,\n slicer: Slicer\n): Promise<boolean> {\n let metas: Automerge.ChangeMetadata[];\n let counts: Map<string, EditCounts> | null;\n try {\n metas = Automerge.getChangesMetaSince(doc, since);\n counts = await editCountsSince(doc, since, slicer.tick);\n } catch (err) {\n console.warn(\n \"[drafts] change grouping: failed to read changes for member:\",\n member.url,\n err\n );\n return true;\n }\n if (!counts) return false;\n metas.forEach((meta, seq) => {\n if (createdAt !== undefined && meta.time && meta.time < createdAt) return;\n out.push({\n memberUrl: member.url,\n hash: meta.hash,\n time: meta.time,\n actor: meta.actor,\n seq,\n ...(counts!.get(meta.hash) ?? { additions: 0, deletions: 0 }),\n });\n });\n return true;\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(), { timeout: 200 });\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 waits for idle time. Resolves\n// false once the run is aborted.\ntype Slicer = { tick: () => Promise<boolean> };\n\nfunction createSlicer(isAborted: () => boolean): 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 await idle();\n sliceStart = performance.now();\n return !isAborted();\n },\n };\n}\n\nfunction sumCounts(rows: PendingChange[]): EditCounts {\n let additions = 0;\n let deletions = 0;\n for (const row of rows) {\n additions += row.additions;\n deletions += row.deletions;\n }\n return { additions, deletions };\n}\n\n// Aggregate a group's changes (newest-first) down to the ChangeGroup a\n// timeline row renders.\nfunction buildGroup(rowsNewestFirst: PendingChange[]): ChangeGroup {\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 ...sumCounts(rowsNewestFirst),\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(repo: Repo): 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: () => 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 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 = () => {\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 getDocCreationTime(repo, 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 slicer = createSlicer(isAborted);\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 if (!(await collectMemberRows(tails, member, doc, since, createdAt, slicer))) {\n return;\n }\n }\n if (isAborted()) return;\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 appendTail(changeGroupHandle, newestStored, tails, frontier);\n } else {\n await rebuildAll(\n changeGroupHandle,\n sources,\n createdAt,\n frontier,\n isAborted,\n slicer\n );\n }\n }\n\n // Incremental append: extend the newest stored group with the tail\n // (accumulate counts, union actors, bump the anchor) and/or open new groups\n // above it. No stored aggregate is ever decomposed.\n function appendTail(\n changeGroupHandle: DocHandle<ChangeGroupDoc>,\n newestStored: ChangeGroup,\n tailsNewestFirst: PendingChange[],\n frontier: Record<AutomergeUrl, UrlHeads>\n ): 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 changeGroupHandle.change((d) => {\n for (const rows of freshGroups) {\n const group = buildGroup(rows);\n d.groups[group.id] = group;\n }\n if (attaches) {\n extendGroup(d, newestStored, oldestGroup, sumCounts(oldestGroup));\n }\n advanceConsumed(d, frontier);\n });\n }\n\n // Move each member's consumed marker to its gathered frontier. Assigning an\n // array is never a no-op in Automerge, so unchanged markers are left alone.\n function advanceConsumed(\n d: ChangeGroupDoc,\n frontier: Record<AutomergeUrl, UrlHeads>\n ): void {\n for (const [url, heads] of Object.entries(frontier)) {\n if (!sameHeads(d.computedThrough[url as AutomergeUrl], heads)) {\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 (decoded in idle\n // slices), re-split, and write the groups in one change. A stored group\n // whose id, span, and change count match is left untouched (no write when\n // another client's grouping update syncs in). Stale ids and consumed\n // markers settle in the same 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 slicer: Slicer\n ): Promise<void> {\n const rows: PendingChange[] = [];\n for (const { member, doc } of sources) {\n const since = member.clonedAt ? decodeHeads(member.clonedAt) : [];\n if (!(await collectMemberRows(rows, member, doc, since, createdAt, slicer))) {\n return;\n }\n }\n if (isAborted()) return;\n rows.sort(newestFirst);\n const groupsRows = splitIntoGroups(rows);\n const expectedIds = new Set(groupsRows.map(groupId));\n\n const stored = changeGroupHandle.doc()?.groups ?? {};\n const fresh = groupsRows\n .filter((groupRows) => {\n const existing = stored[groupId(groupRows)];\n return !(\n existing &&\n existing.changeCount === groupRows.length &&\n existing.startTime === groupRows[groupRows.length - 1].time &&\n existing.endTime === groupRows[0].time\n );\n })\n .map(buildGroup);\n\n changeGroupHandle.change((d) => {\n for (const group of fresh) 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 advanceConsumed(d, frontier);\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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"}