@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
@@ -30,7 +30,6 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@automerge/automerge-repo-react-hooks": "2.6.0-subduction.46",
33
- "@automerge/react": "^2.6.0-subduction.9",
34
33
  "@inkandswitch/patchwork-bootloader": "^0.7.2",
35
34
  "@inkandswitch/patchwork-filesystem": "^0.2.9",
36
35
  "@inkandswitch/patchwork-plugins": "^1.2.5",
@@ -0,0 +1 @@
1
+ automerge:4Cq26tUMMhGQpo6PJv8XzByWKtj8
@@ -1 +0,0 @@
1
- {"version":3,"file":"DraftStateProvider-DX8b_RgQ.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 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\";\n\n// Only the writing client knows which Automerge actor ids are its own. Each\n// local change reveals one id, which the ActorRecorder attributes to the\n// current user's contact. Changes seen before dependencies resolve are\n// buffered.\nexport function createActorRecorder(element: HTMLElement): ActorRecorder {\n let contactUrl: AutomergeUrl | null = null;\n let attributionHandle: DocHandle<ActorAttributionDoc> | null = null;\n const recordedActorIds = new Set<string>();\n const pendingActorIds = new Set<string>();\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 return {\n recordLocalChange(doc) {\n if (disposed) return;\n let actorId: string;\n try {\n actorId = Automerge.getActorId(doc);\n } catch {\n return;\n }\n if (recordedActorIds.has(actorId)) return;\n pendingActorIds.add(actorId);\n flushPendingActors();\n },\n setAttributionHandle(handle) {\n if (disposed) return;\n attributionHandle = handle;\n flushPendingActors();\n },\n dispose() {\n disposed = true;\n pendingActorIds.clear();\n unsubscribe();\n },\n };\n\n function flushPendingActors(): void {\n if (!attributionHandle || !contactUrl || pendingActorIds.size === 0) return;\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 recordLocalChange: (doc: Automerge.Doc<unknown>) => void;\n setAttributionHandle: (handle: DocHandle<ActorAttributionDoc>) => 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 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, type DraftRouter } 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// 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`.\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 let checkedOutHandle: DocHandle<CheckedOutDraft> | null = null;\n let draftRouter: DraftRouter | 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 // `draft:checked-out` subscribers that arrived before the ephemeral\n // CheckedOutDraft doc was created; flushed once it exists.\n const pendingCheckedOutSubscribers = new Set<(url: AutomergeUrl) => void>();\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\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);\n // Keep every timeline's ChangeGroupDoc current whether or not the sidebar\n // is open. Member-doc listeners drive updates between list recomputes.\n const changeGrouper = createChangeGrouper(repo, {\n onLocalChange: actorRecorder.recordLocalChange,\n });\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 // 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 recomputeList();\n };\n const onHostDocChange = () => scheduleRewalk();\n // The checkout doc changed: its checkpoint may have moved, so re-publish\n // every live `draft:baseline` subscriber.\n const onCheckedOutChange = () => notifyBaselines();\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 syncMainDraftClones();\n recomputeList();\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 recomputeList();\n };\n\n const ready: Promise<void> = (async () => {\n const handle = await repo.find<HasDrafts>(docUrl);\n if (disposed) return;\n hostDocHandle = handle;\n\n // Eagerly create the main draft (a real doc, not the virtual fallback) so\n // its ChangeGroupDoc has a persistent home even if the sidebar is never\n // opened. Main's clones are identity mappings — nothing is forked;\n // the only host-doc side effect is the `mainDraftUrl` scalar, which the\n // timeline's `@patchwork` path skip filters out. App-global datatypes the\n // draft machinery never treats as content are left alone.\n const hostType = handle.doc()?.[\"@patchwork\"]?.type;\n if (hostType == null || !SKIPPED_DATATYPES.has(hostType)) {\n try {\n const mainDraft = await ensureMainDraft(repo, handle);\n if (disposed) return;\n // The attribution doc is shared by every timeline on this host doc;\n // once it resolves the ActorRecorder starts stamping local actor ids.\n const attribution = await ensureActorAttribution(repo, mainDraft);\n if (disposed) return;\n actorRecorder.setAttributionHandle(attribution);\n } catch (err) {\n console.warn(\"[drafts] failed to eagerly create main draft:\", err);\n }\n }\n\n // Eagerly create the ephemeral CheckedOutDraft so the sidebar can render\n // its \"Main\" card and write `checkedOut` even before any drafts exist on\n // the host doc.\n checkedOutHandle = repo.create<CheckedOutDraft>({ checkedOut: null });\n checkedOutHandle.on(\"change\", onCheckedOutChange);\n draftRouter = createDraftRouter(checkedOutHandle);\n const checkedOutUrl = checkedOutHandle.url;\n for (const respond of pendingCheckedOutSubscribers) {\n respond(checkedOutUrl);\n }\n pendingCheckedOutSubscribers.clear();\n // A checkpoint may already have synced in before this provider mounted.\n notifyBaselines();\n\n handle.on(\"change\", onHostDocChange);\n scheduleRewalk();\n recomputeList();\n })();\n ready.catch((err) => {\n console.error(`[drafts] failed to initialize draft-state provider:`, err);\n });\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 if (checkedOutHandle) {\n respond(checkedOutHandle.url);\n return;\n }\n pendingCheckedOutSubscribers.add(respond);\n return () => pendingCheckedOutSubscribers.delete(respond);\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 return () => listSubscribers.delete(respond);\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 respond(currentBaseline(target));\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) baselineSubscribers.delete(target);\n };\n });\n return;\n }\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 changeGrouper.dispose();\n actorRecorder.dispose();\n draftRouter?.dispose();\n draftRouter = null;\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n element.removeEventListener(\"patchwork:mounted\", onMounted);\n element.removeEventListener(\"patchwork:unmounted\", onUnmounted);\n if (hostDocHandle) hostDocHandle.off(\"change\", onHostDocChange);\n if (mainDraftHandle) mainDraftHandle.off(\"change\", onTrackedChange);\n for (const [, h] of trackedDrafts) h.off(\"change\", onTrackedChange);\n mainDraftHandle = null;\n trackedDrafts.clear();\n pendingCheckedOutSubscribers.clear();\n listSubscribers.clear();\n baselineSubscribers.clear();\n mountCounts.clear();\n skipVerdicts.clear();\n if (checkedOutHandle) {\n checkedOutHandle.off(\"change\", onCheckedOutChange);\n repo.delete(checkedOutHandle.url);\n }\n checkedOutHandle = null;\n hostDocHandle = null;\n };\n\n function scheduleRewalk(): void {\n if (disposed) return;\n if (!hostDocHandle || !checkedOutHandle) 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 );\n if (disposed) return;\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 recomputeList();\n if (rewalkPending) {\n rewalkPending = false;\n scheduleRewalk();\n }\n }\n })();\n }\n\n // Recompute the `draft:list` value and push it to subscribers when it\n // actually changed. The ChangeGrouper follows the same triggers.\n function recomputeList(): void {\n if (disposed) return;\n 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 // 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 function notifyBaselines(): void {\n for (const [target, set] of baselineSubscribers) {\n const baseline = currentBaseline(target);\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 syncMainDraftClones();\n recomputeList();\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 // Keep the main draft's identity clone map in step with the live mounted set:\n // every confirmed not-skipped mounted doc gets an identity entry (`cloneUrl\n // === url`, empty fork heads). Additive only — entries are never removed, so\n // main's membership (and history) is stable across unmounts. Writes are\n // diffed, so this is a no-op once everything mounted is already recorded.\n function syncMainDraftClones(): void {\n if (disposed || !mainDraftHandle) 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): 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 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":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"draft-docs-Bi8K1y3a.js","sources":["../../src/change-group-cache.ts","../../src/draft-docs.ts"],"sourcesContent":["import {\n decodeHeads,\n encodeHeads,\n isValidAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type DocHandleChangePayload,\n type Repo,\n type UrlHeads,\n} from \"@automerge/automerge-repo/slim\";\nimport * as Automerge from \"@automerge/automerge/slim\";\n\nimport type {\n ChangeGroup,\n ChangeGroupDoc,\n DraftDoc,\n DraftMemberDoc,\n} from \"./draft-types.js\";\n\n// Bump to discard every existing group doc's contents (they self-rebuild).\nexport const CHANGE_GROUP_DOC_VERSION = 1;\n\n// A pause between consecutive changes longer than this starts a new group:\n// bursts of continuous editing read as a single row, however long they run,\n// and any ten-minute-plus lull splits the timeline. Baked into the group\n// doc — changing it invalidates and rebuilds every document.\nexport const INACTIVITY_GAP_MS = 10 * 60 * 1000;\n\n// How long grouping may hold the main thread before yielding to idle time.\nconst SLICE_BUDGET_MS = 8;\n\n// Coalesce bursts of member-doc change events into one grouping update.\nconst GROUPING_DEBOUNCE_MS = 250;\n\n// Change-event sources that mean a LOCAL edit (made through this client's\n// doc instance), as opposed to synced/merged remote changes.\nconst LOCAL_PATCH_SOURCES = new Set([\"change\", \"changeAt\", \"emptyChange\"]);\n\n// One timeline the ChangeGrouper is responsible for: the DraftDoc that owns it\n// (main included — the main draft is always real), the member docs whose\n// interleaved changes make it up, and the host doc whose creation time is\n// the \"before this document existed\" cutoff.\nexport type TimelineGroupingSpec = {\n draftHandle: DocHandle<DraftDoc>;\n members: DraftMemberDoc[];\n rootDocUrl: AutomergeUrl;\n};\n\nexport type ChangeGrouper = {\n // Reconcile the timelines to keep grouped (ordered by priority). Attaches\n // member-doc change listeners that drive incremental grouping updates;\n // timelines absent from the list are torn down.\n setTimelines: (specs: TimelineGroupingSpec[]) => void;\n dispose: () => void;\n};\n\nexport type ChangeGrouperOptions = {\n // Called when a watched member doc receives a local change — i.e. the\n // current user just wrote with that doc instance's actor id. Feeds the\n // ActorRecorder (see actor-attribution.ts).\n onLocalChange?: (doc: Automerge.Doc<unknown>) => void;\n};\n\n// Resolve a draft's change-group doc, creating it and stamping\n// `changeGroupDocUrl` the first time. A document whose format version or\n// grouping parameter no longer matches is emptied in place and rebuilt.\nexport async function ensureChangeGroupDoc(\n repo: Repo,\n draftHandle: DocHandle<DraftDoc>\n): Promise<DocHandle<ChangeGroupDoc>> {\n const draft = draftHandle.doc();\n const existingUrl = getChangeGroupDocUrl(draft);\n if (existingUrl && isValidAutomergeUrl(existingUrl)) {\n const handle = await repo.find<ChangeGroupDoc>(existingUrl);\n const doc = handle.doc();\n if (\n doc &&\n (doc.version !== CHANGE_GROUP_DOC_VERSION ||\n doc.inactivityGapMs !== INACTIVITY_GAP_MS)\n ) {\n handle.change((d) => {\n d.version = CHANGE_GROUP_DOC_VERSION;\n d.inactivityGapMs = INACTIVITY_GAP_MS;\n d.groups = {};\n d.computedThrough = {};\n });\n }\n if (!draft?.changeGroupDocUrl) {\n draftHandle.change((d) => {\n if (!d.changeGroupDocUrl) d.changeGroupDocUrl = existingUrl;\n });\n }\n return handle;\n }\n\n const changeGroupHandle = repo.create<ChangeGroupDoc>({\n \"@patchwork\": { type: \"change-group\" },\n version: CHANGE_GROUP_DOC_VERSION,\n inactivityGapMs: INACTIVITY_GAP_MS,\n groups: {},\n computedThrough: {},\n });\n draftHandle.change((d) => {\n if (!d.changeGroupDocUrl) d.changeGroupDocUrl = changeGroupHandle.url;\n });\n // A concurrent creator may have won the stamp; honor whichever pointer\n // settled (our fresh doc is then an accepted orphan, same as mainDraftUrl).\n const settled = draftHandle.doc()?.changeGroupDocUrl;\n if (\n settled &&\n settled !== changeGroupHandle.url &&\n isValidAutomergeUrl(settled)\n ) {\n return repo.find<ChangeGroupDoc>(settled);\n }\n return changeGroupHandle;\n}\n\nfunction getChangeGroupDocUrl(\n draft: DraftDoc | undefined\n): AutomergeUrl | undefined {\n const legacyDraft = draft as\n | (DraftDoc & { changeGroupCacheUrl?: AutomergeUrl })\n | undefined;\n return draft?.changeGroupDocUrl ?? legacyDraft?.changeGroupCacheUrl;\n}\n\n// Work out one change's rough edit magnitude by diffing it against its parents\n// and counting its patches: splice lengths and insert counts as additions, del\n// lengths as deletions, everything else (put / inc / mark / …) as one addition.\n// `@patchwork` metadata paths are ignored. A pure function of immutable\n// history, so each change is diffed once, here, and only the group aggregates\n// are kept.\nexport function computeEditCounts(\n doc: Automerge.Doc<unknown>,\n hash: string,\n deps: string[]\n): { additions: number; deletions: number } {\n try {\n return countPatches(\n Automerge.diff(\n doc,\n deps as unknown as Automerge.Heads,\n [hash] as unknown as Automerge.Heads\n )\n );\n } catch (err) {\n console.warn(\"[drafts] failed to diff change for edit counts:\", hash, err);\n return { additions: 0, deletions: 0 };\n }\n}\n\n// The same +/- magnitude over an arbitrary head range (`from` → `to`), e.g.\n// the eye's diff span between the baseline and the scrubbed head.\nexport function computeRangeEditCounts(\n doc: Automerge.Doc<unknown>,\n from: string[],\n to: string[]\n): { additions: number; deletions: number } {\n try {\n return countPatches(\n Automerge.diff(\n doc,\n from as unknown as Automerge.Heads,\n to as unknown as Automerge.Heads\n )\n );\n } catch (err) {\n console.warn(\"[drafts] failed to diff range for edit counts:\", err);\n return { additions: 0, deletions: 0 };\n }\n}\n\n// Shared patch-counting rules for the two diff flavors above.\nfunction countPatches(patches: Automerge.Patch[]): {\n additions: number;\n deletions: number;\n} {\n let additions = 0;\n let deletions = 0;\n for (const patch of patches) {\n if (patch.path[0] === \"@patchwork\") continue;\n if (patch.action === \"splice\") {\n additions += (patch.value as string).length;\n } else if (patch.action === \"insert\") {\n additions += Array.isArray((patch as { values?: unknown[] }).values)\n ? (patch as { values: unknown[] }).values.length\n : 1;\n } else if (patch.action === \"del\") {\n deletions += (patch as { length?: number }).length ?? 1;\n } else {\n additions += 1;\n }\n }\n return { additions, deletions };\n}\n\n// When was a document created, as a Unix SECONDS timestamp? Reads the doc's\n// full history and returns its first change's time (the creation change).\n// Returns undefined when the doc, its history, or that time can't be resolved,\n// in which case callers skip the \"before creation\" filter rather than hiding\n// everything.\nexport async function getDocCreationTime(\n repo: Repo,\n url: AutomergeUrl | undefined\n): Promise<number | undefined> {\n if (!url) return undefined;\n try {\n const handle = await repo.find<unknown>(url);\n const doc = handle.doc();\n if (!doc) return undefined;\n const metas = Automerge.getChangesMetaSince(doc, []);\n return metas[0]?.time || undefined;\n } catch (err) {\n console.warn(\"[drafts] failed to resolve creation time for:\", url, err);\n return undefined;\n }\n}\n\n// One member change awaiting aggregation. `seq` is the change's index within\n// its member's gathered metas, used only to break same-second timestamp ties\n// (meta.time is second-resolution) with the doc's own causal order.\ntype PendingChange = {\n memberUrl: AutomergeUrl;\n doc: Automerge.Doc<unknown>;\n hash: string;\n deps: string[];\n time: number;\n actor: string;\n seq: number;\n};\n\n// Newest first by timestamp, per-doc causal order breaking same-second ties.\n// The sidebar's on-demand scrub resolution MUST order identically, or the\n// scrubber's index math drifts from `changeCount`.\nfunction newestFirst(a: PendingChange, b: PendingChange): number {\n return b.time - a.time || b.seq - a.seq;\n}\n\n// Fold a flat, newest-first list of changes into groups: consecutive changes\n// stay together while the pause between them is at most the inactivity gap.\nfunction splitIntoGroups(\n rowsNewestFirst: PendingChange[]\n): PendingChange[][] {\n const groups: PendingChange[][] = [];\n let window: PendingChange[] = [];\n let prevTimeMs: number | null = null;\n for (const row of rowsNewestFirst) {\n const timeMs = row.time * 1000;\n // Rows arrive newest-first, so the previous row is this change's newer\n // neighbour; a gap larger than the threshold between them is a lull.\n if (\n prevTimeMs !== null &&\n prevTimeMs - timeMs > INACTIVITY_GAP_MS &&\n window.length > 0\n ) {\n groups.push(window);\n window = [];\n }\n window.push(row);\n prevTimeMs = timeMs;\n }\n if (window.length > 0) groups.push(window);\n return groups;\n}\n\nfunction groupId(rowsNewestFirst: PendingChange[]): string {\n return `tg-${rowsNewestFirst[0].hash}`;\n}\n\n// Append `member`'s changes since `since` onto `out`, dropping anything from\n// before the root document was created (a member dragged in after the fact\n// would otherwise contribute pre-existing history that reads as noise). `seq`\n// still reflects each change's position in the gathered metas, so filtering\n// doesn't disturb the tie-break ordering.\nfunction collectMemberRows(\n out: PendingChange[],\n member: DraftMemberDoc,\n doc: Automerge.Doc<unknown>,\n since: Automerge.Heads,\n createdAt: number | undefined\n): void {\n let metas;\n try {\n metas = Automerge.getChangesMetaSince(doc, since);\n } catch (err) {\n console.warn(\n \"[drafts] change grouping: failed to read changes for member:\",\n member.url,\n err\n );\n return;\n }\n metas.forEach((meta, seq) => {\n if (createdAt !== undefined && meta.time && meta.time < createdAt) return;\n out.push({\n memberUrl: member.url,\n doc,\n hash: meta.hash,\n deps: meta.deps,\n time: meta.time,\n actor: meta.actor,\n seq,\n });\n });\n}\n\nfunction dedupedActors(rowsNewestFirst: PendingChange[]): string[] {\n const actors: string[] = [];\n for (const row of rowsNewestFirst) {\n if (!actors.includes(row.actor)) actors.push(row.actor);\n }\n return actors;\n}\n\n// Yield to the main thread between diff slices.\nfunction idle(): Promise<void> {\n return new Promise((resolve) => {\n if (typeof requestIdleCallback === \"function\") {\n requestIdleCallback(() => resolve());\n } else {\n setTimeout(resolve, 0);\n }\n });\n}\n\n// Time-budgeted cooperative slicing: `tick()` resolves immediately while the\n// current slice has budget left, otherwise runs `onYield` (flush pending\n// writes) and waits for idle time. Resolves false once the run is aborted.\ntype Slicer = { tick: () => Promise<boolean> };\n\nfunction createSlicer(isAborted: () => boolean, onYield: () => void): Slicer {\n let sliceStart = performance.now();\n return {\n async tick(): Promise<boolean> {\n if (isAborted()) return false;\n if (performance.now() - sliceStart < SLICE_BUDGET_MS) return true;\n onYield();\n await idle();\n sliceStart = performance.now();\n return !isAborted();\n },\n };\n}\n\n// Diff every change in a group (newest-first, sliced) and aggregate the\n// result down to the ChangeGroup a timeline row renders. Returns null when\n// the run was aborted mid-diff.\nasync function buildGroup(\n rowsNewestFirst: PendingChange[],\n slicer: Slicer\n): Promise<ChangeGroup | null> {\n let additions = 0;\n let deletions = 0;\n for (const row of rowsNewestFirst) {\n if (!(await slicer.tick())) return null;\n const counts = computeEditCounts(row.doc, row.hash, row.deps);\n additions += counts.additions;\n deletions += counts.deletions;\n }\n const newest = rowsNewestFirst[0];\n const oldest = rowsNewestFirst[rowsNewestFirst.length - 1];\n return {\n id: groupId(rowsNewestFirst),\n startTime: oldest.time,\n endTime: newest.time,\n newestMemberUrl: newest.memberUrl,\n newestHash: newest.hash,\n actors: dedupedActors(rowsNewestFirst),\n additions,\n deletions,\n changeCount: rowsNewestFirst.length,\n };\n}\n\nfunction byMemberUrl(a: DraftMemberDoc, b: DraftMemberDoc): number {\n return a.url < b.url ? -1 : a.url > b.url ? 1 : 0;\n}\n\nexport function sameHeads(\n a: UrlHeads | undefined,\n b: UrlHeads | undefined\n): boolean {\n if (a === b) return true;\n if (!a || !b || a.length !== b.length) return false;\n const set = new Set(b);\n return a.every((h) => set.has(h));\n}\n\n// Groups each timeline's changes into its ChangeGroupDoc. It owns one\n// background task per timeline, listens to member docs for edits, and updates\n// newest groups first while older history backfills. One global runner\n// processes timelines sequentially in the priority order it was given.\nexport function createChangeGrouper(\n repo: Repo,\n options: ChangeGrouperOptions = {}\n): ChangeGrouper {\n // Change listeners on the docs a timeline reads (originals for main,\n // clones for drafts). `handle` is null while the doc is still resolving —\n // the slot is reserved up front so concurrent syncs don't double-attach.\n type SourceListener = {\n handle: DocHandle<unknown> | null;\n onChange: (payload: DocHandleChangePayload<unknown>) => void;\n };\n\n type Task = {\n key: AutomergeUrl; // the DraftDoc url\n spec: TimelineGroupingSpec;\n listeners: Map<AutomergeUrl, SourceListener>;\n queued: boolean;\n debounce: ReturnType<typeof setTimeout> | null;\n };\n\n const tasks = new Map<AutomergeUrl, Task>();\n const queue: AutomergeUrl[] = [];\n let running = false;\n let disposed = false;\n\n // Host-doc creation times, resolved once per root url.\n const creationTimes = new Map<AutomergeUrl, Promise<number | undefined>>();\n const creationTime = (url: AutomergeUrl): Promise<number | undefined> => {\n let cached = creationTimes.get(url);\n if (!cached) {\n cached = getDocCreationTime(repo, url);\n creationTimes.set(url, cached);\n }\n return cached;\n };\n\n function setTimelines(specs: TimelineGroupingSpec[]): void {\n if (disposed) return;\n const keep = new Set(specs.map((s) => s.draftHandle.url));\n for (const [key, task] of [...tasks]) {\n if (!keep.has(key)) removeTask(task);\n }\n for (const spec of specs) {\n const key = spec.draftHandle.url;\n let task = tasks.get(key);\n const membersChanged =\n !task || !sameMemberSets(task.spec.members, spec.members);\n if (!task) {\n task = { key, spec, listeners: new Map(), queued: false, debounce: null };\n tasks.set(key, task);\n } else {\n task.spec = spec;\n }\n void ensureListeners(task);\n if (membersChanged) schedule(key);\n }\n }\n\n function dispose(): void {\n disposed = true;\n for (const [, task] of [...tasks]) removeTask(task);\n queue.length = 0;\n }\n\n return { setTimelines, dispose };\n\n function removeTask(task: Task): void {\n if (task.debounce) clearTimeout(task.debounce);\n task.debounce = null;\n for (const [, { handle, onChange }] of task.listeners) {\n handle?.off(\"change\", onChange);\n }\n task.listeners.clear();\n // A queued entry is skipped by the pump once the task is gone.\n tasks.delete(task.key);\n }\n\n function sameMemberSets(a: DraftMemberDoc[], b: DraftMemberDoc[]): boolean {\n if (a.length !== b.length) return false;\n const key = (m: DraftMemberDoc) => `${m.url}:${m.cloneUrl ?? \"\"}`;\n const set = new Set(a.map(key));\n return b.every((m) => set.has(key(m)));\n }\n\n // Keep exactly one change listener per member source doc; edits schedule a\n // debounced grouping update for the owning timeline.\n async function ensureListeners(task: Task): Promise<void> {\n const wanted = new Set(\n task.spec.members.map((m) => m.cloneUrl ?? m.url)\n );\n for (const [url, { handle, onChange }] of [...task.listeners]) {\n if (!wanted.has(url)) {\n handle?.off(\"change\", onChange);\n task.listeners.delete(url);\n }\n }\n for (const url of wanted) {\n if (task.listeners.has(url)) continue;\n const onChange = (payload: DocHandleChangePayload<unknown>) => {\n if (\n options.onLocalChange &&\n LOCAL_PATCH_SOURCES.has(payload.patchInfo.source) &&\n payload.doc\n ) {\n options.onLocalChange(payload.doc);\n }\n if (task.debounce) clearTimeout(task.debounce);\n task.debounce = setTimeout(() => {\n task.debounce = null;\n schedule(task.key);\n }, GROUPING_DEBOUNCE_MS);\n };\n const slot: SourceListener = { handle: null, onChange };\n task.listeners.set(url, slot);\n try {\n const handle = await repo.find<unknown>(url);\n // The task was torn down or the slot dropped while resolving.\n if (disposed || tasks.get(task.key) !== task) return;\n if (task.listeners.get(url) !== slot) return;\n handle.on(\"change\", onChange);\n slot.handle = handle;\n } catch (err) {\n if (task.listeners.get(url) === slot) task.listeners.delete(url);\n console.warn(\n \"[drafts] change grouping: failed to watch member:\",\n url,\n err\n );\n }\n }\n }\n\n function schedule(key: AutomergeUrl): void {\n if (disposed) return;\n const task = tasks.get(key);\n if (!task || task.queued) return;\n task.queued = true;\n queue.push(key);\n void pump();\n }\n\n async function pump(): Promise<void> {\n if (running || disposed) return;\n running = true;\n try {\n while (queue.length > 0 && !disposed) {\n const key = queue.shift()!;\n const task = tasks.get(key);\n if (!task) continue;\n task.queued = false;\n try {\n await updateTimelineGroups(task);\n } catch (err) {\n console.warn(\n \"[drafts] change grouping failed for:\",\n key,\n err\n );\n }\n }\n } finally {\n running = false;\n }\n }\n\n // Update one timeline. The common case appends the unconsumed\n // tail onto the newest group; anything that would reshape older groups\n // (late-syncing changes, a fresh document) falls back to a full rebuild\n // that reuses stored groups wherever they come out identical.\n async function updateTimelineGroups(task: Task): Promise<void> {\n const spec = task.spec;\n // The run is stale once the task was replaced or torn down (a newer spec\n // re-queues itself), or the ChangeGrouper was disposed.\n const isAborted = () => disposed || tasks.get(task.key) !== task;\n\n if (!spec.draftHandle.doc()) return;\n const changeGroupHandle = await ensureChangeGroupDoc(\n repo,\n spec.draftHandle\n );\n const createdAt = await creationTime(spec.rootDocUrl);\n if (isAborted()) return;\n\n // Resolve member sources, sorted by member url so cross-doc timestamp\n // ties interleave identically on every client.\n const members = [...spec.members].sort(byMemberUrl);\n const sources: { member: DraftMemberDoc; doc: Automerge.Doc<unknown> }[] =\n [];\n for (const member of members) {\n try {\n const handle = await repo.find<unknown>(member.cloneUrl ?? member.url);\n const doc = handle.doc();\n if (doc) sources.push({ member, doc: doc as Automerge.Doc<unknown> });\n } catch (err) {\n console.warn(\n \"[drafts] change grouping: failed to resolve member:\",\n member,\n err\n );\n }\n }\n if (isAborted()) return;\n\n const changeGroupDoc = changeGroupHandle.doc();\n if (!changeGroupDoc) return;\n const computedThrough = changeGroupDoc.computedThrough ?? {};\n\n // Each member's frontier as of this gather; the consumed marker advances\n // to exactly these once the run completes, so the next run's\n // getChangesMetaSince yields precisely the unconsumed tail.\n const frontier: Record<AutomergeUrl, UrlHeads> = {};\n const tails: PendingChange[] = [];\n for (const { member, doc } of sources) {\n frontier[member.url] = encodeHeads(Automerge.getHeads(doc));\n const consumed = computedThrough[member.url];\n const since = consumed\n ? decodeHeads(consumed)\n : member.clonedAt\n ? decodeHeads(member.clonedAt)\n : [];\n collectMemberRows(tails, member, doc, since, createdAt);\n }\n\n if (tails.length === 0) {\n // Nothing new to group; just record any frontier movement (e.g. members\n // whose unconsumed changes were all filtered out, or brand-new members\n // with no post-fork changes yet).\n const stale = Object.entries(frontier).filter(\n ([url, heads]) => !sameHeads(computedThrough[url as AutomergeUrl], heads)\n );\n if (stale.length > 0) {\n changeGroupHandle.change((d) => {\n for (const [url, heads] of stale) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n return;\n }\n\n tails.sort(newestFirst);\n\n // Fast path: every new change lands on or after the newest stored group\n // (extending it or opening newer ones) without bridging into the group\n // below it — the overwhelmingly common live-editing case. Everything else\n // (first build, members without a consumed marker, late-syncing changes\n // with old timestamps) rebuilds via the full pass.\n const stored = Object.values(changeGroupDoc.groups ?? {}).sort(\n (a, b) => b.endTime - a.endTime\n );\n const newestStored = stored[0];\n const secondStored = stored[1];\n const tailOldestMs = tails[tails.length - 1].time * 1000;\n const fastOk =\n !!newestStored &&\n tailOldestMs >= newestStored.startTime * 1000 - INACTIVITY_GAP_MS &&\n (!secondStored ||\n tailOldestMs - secondStored.endTime * 1000 > INACTIVITY_GAP_MS);\n\n if (fastOk) {\n await appendTail(\n changeGroupHandle,\n newestStored,\n tails,\n frontier,\n isAborted\n );\n } else {\n await rebuildAll(\n changeGroupHandle,\n sources,\n createdAt,\n frontier,\n isAborted\n );\n }\n }\n\n // Incremental append: diff only the tail, then extend the newest stored\n // group (accumulate counts, union actors, bump the anchor) and/or open new\n // groups above it. No stored aggregate is ever decomposed.\n async function appendTail(\n changeGroupHandle: DocHandle<ChangeGroupDoc>,\n newestStored: ChangeGroup,\n tailsNewestFirst: PendingChange[],\n frontier: Record<AutomergeUrl, UrlHeads>,\n isAborted: () => boolean\n ): Promise<void> {\n const tailGroups = splitIntoGroups(tailsNewestFirst);\n const oldestGroup = tailGroups[tailGroups.length - 1];\n const oldestGroupOldestMs =\n oldestGroup[oldestGroup.length - 1].time * 1000;\n // The oldest run of new changes merges into the stored group when no lull\n // separates them (it may even start inside the stored span).\n const attaches =\n oldestGroupOldestMs <= newestStored.endTime * 1000 + INACTIVITY_GAP_MS;\n const freshGroups = attaches ? tailGroups.slice(0, -1) : tailGroups;\n\n const slicer = createSlicer(isAborted, () => {});\n const built: ChangeGroup[] = [];\n for (const rows of freshGroups) {\n const group = await buildGroup(rows, slicer);\n if (group === null) return;\n built.push(group);\n }\n\n let extensionSums: { additions: number; deletions: number } | null = null;\n if (attaches) {\n let additions = 0;\n let deletions = 0;\n for (const row of oldestGroup) {\n if (!(await slicer.tick())) return;\n const counts = computeEditCounts(row.doc, row.hash, row.deps);\n additions += counts.additions;\n deletions += counts.deletions;\n }\n extensionSums = { additions, deletions };\n }\n\n if (isAborted()) return;\n changeGroupHandle.change((d) => {\n for (const group of built) d.groups[group.id] = group;\n if (attaches && extensionSums) {\n extendGroup(d, newestStored, oldestGroup, extensionSums);\n }\n for (const [url, heads] of Object.entries(frontier)) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n\n // Fold a tail run into the newest stored group inside an open change():\n // re-read the group from the live doc so a concurrently synced extension is\n // extended further rather than clobbered.\n function extendGroup(\n d: ChangeGroupDoc,\n storedSnapshot: ChangeGroup,\n tailNewestFirst: PendingChange[],\n sums: { additions: number; deletions: number }\n ): void {\n const tailNewest = tailNewestFirst[0];\n const tailOldest = tailNewestFirst[tailNewestFirst.length - 1];\n const base = d.groups[storedSnapshot.id];\n if (!base) {\n // The stored group vanished under us (concurrent rewrite); keep the\n // tail as its own group rather than losing it — the next full pass\n // reconciles the shape.\n d.groups[`tg-${tailNewest.hash}`] = {\n id: `tg-${tailNewest.hash}`,\n startTime: tailOldest.time,\n endTime: tailNewest.time,\n newestMemberUrl: tailNewest.memberUrl,\n newestHash: tailNewest.hash,\n actors: dedupedActors(tailNewestFirst),\n additions: sums.additions,\n deletions: sums.deletions,\n changeCount: tailNewestFirst.length,\n };\n return;\n }\n\n // The tail is newer causally, but a merge can deliver changes stamped\n // inside the stored span; only a strictly later timestamp moves the\n // anchor.\n const newer = tailNewest.time > base.endTime;\n const tailActors = dedupedActors(tailNewestFirst);\n const baseActors = [...base.actors];\n const actors = newer\n ? [...tailActors, ...baseActors.filter((a) => !tailActors.includes(a))]\n : [...baseActors, ...tailActors.filter((a) => !baseActors.includes(a))];\n\n const extended: ChangeGroup = {\n id: newer ? `tg-${tailNewest.hash}` : base.id,\n startTime: Math.min(base.startTime, tailOldest.time),\n endTime: Math.max(base.endTime, tailNewest.time),\n newestMemberUrl: newer ? tailNewest.memberUrl : base.newestMemberUrl,\n newestHash: newer ? tailNewest.hash : base.newestHash,\n actors,\n additions: base.additions + sums.additions,\n deletions: base.deletions + sums.deletions,\n changeCount: base.changeCount + tailNewestFirst.length,\n };\n if (extended.id !== storedSnapshot.id) delete d.groups[storedSnapshot.id];\n d.groups[extended.id] = extended;\n }\n\n // Full rebuild: regather every member's post-fork history, re-split, and\n // diff newest-first in idle slices — flushing completed groups as each\n // slice ends so recent history paints while older history backfills. A\n // stored group whose id, span, and change count match is reused without\n // re-diffing (cheap warm restarts, and no redundant work when another\n // client's grouping update syncs in). Stale ids and consumed markers settle\n // in the final write.\n async function rebuildAll(\n changeGroupHandle: DocHandle<ChangeGroupDoc>,\n sources: { member: DraftMemberDoc; doc: Automerge.Doc<unknown> }[],\n createdAt: number | undefined,\n frontier: Record<AutomergeUrl, UrlHeads>,\n isAborted: () => boolean\n ): Promise<void> {\n const rows: PendingChange[] = [];\n for (const { member, doc } of sources) {\n const since = member.clonedAt ? decodeHeads(member.clonedAt) : [];\n collectMemberRows(rows, member, doc, since, createdAt);\n }\n rows.sort(newestFirst);\n const groupsRows = splitIntoGroups(rows);\n const expectedIds = new Set(groupsRows.map(groupId));\n\n const batch: ChangeGroup[] = [];\n const flush = () => {\n if (batch.length === 0) return;\n changeGroupHandle.change((d) => {\n for (const group of batch) d.groups[group.id] = group;\n });\n batch.length = 0;\n };\n\n const slicer = createSlicer(isAborted, flush);\n for (const groupRows of groupsRows) {\n const id = groupId(groupRows);\n const existing = changeGroupHandle.doc()?.groups?.[id];\n if (\n existing &&\n existing.changeCount === groupRows.length &&\n existing.startTime === groupRows[groupRows.length - 1].time &&\n existing.endTime === groupRows[0].time\n ) {\n continue;\n }\n const group = await buildGroup(groupRows, slicer);\n if (group === null) return; // aborted mid-diff; markers stay put\n batch.push(group);\n }\n\n if (isAborted()) return;\n changeGroupHandle.change((d) => {\n for (const group of batch) d.groups[group.id] = group;\n for (const id of Object.keys(d.groups)) {\n if (!expectedIds.has(id)) delete d.groups[id];\n }\n for (const [url, heads] of Object.entries(frontier)) {\n d.computedThrough[url as AutomergeUrl] = heads;\n }\n });\n }\n}\n","import type {\n DocHandle,\n Repo,\n} from \"@automerge/automerge-repo/slim\";\n\nimport type { DraftDoc, HasDrafts } from \"./draft-types.js\";\n\n// Resolve the host doc's main draft, creating it and stamping\n// `@patchwork.mainDraftUrl` the first time.\nexport async function ensureMainDraft(\n repo: Repo,\n docHandle: DocHandle<HasDrafts>\n): Promise<DocHandle<DraftDoc>> {\n const existingUrl = docHandle.doc()?.[\"@patchwork\"]?.mainDraftUrl;\n if (existingUrl) return repo.find<DraftDoc>(existingUrl);\n\n const mainDraft = repo.create<DraftDoc>({\n \"@patchwork\": { type: \"draft\" },\n isMain: true,\n parent: docHandle.url,\n drafts: [],\n clones: {},\n });\n docHandle.change((d) => {\n // Mutate `@patchwork` in place. Reassigning a spread would carry\n // references to existing Automerge objects into a new object.\n if (!d[\"@patchwork\"]) d[\"@patchwork\"] = {};\n d[\"@patchwork\"]!.mainDraftUrl = mainDraft.url;\n });\n return mainDraft;\n}\n"],"names":[],"mappings":"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"}
@@ -1,67 +0,0 @@
1
- import "@automerge/automerge-repo";
2
- const viewTags = /* @__PURE__ */ new Set(["patchwork-view"]);
3
- function subscribe(element, selector, listener) {
4
- const view = element.closest([...viewTags].join(","));
5
- const dispatchEl = view ?? element;
6
- const channel = new MessageChannel();
7
- const port = channel.port2;
8
- const controller = new AbortController();
9
- const { signal } = controller;
10
- port.addEventListener("message", (event) => {
11
- if (event.data?.type === "change")
12
- listener(event.data.value);
13
- }, { signal });
14
- port.start();
15
- const detail = {
16
- selector,
17
- port: channel.port1
18
- };
19
- dispatchEl.dispatchEvent(new CustomEvent("patchwork:subscribe", {
20
- detail,
21
- bubbles: true,
22
- composed: true
23
- }));
24
- return () => {
25
- if (signal.aborted)
26
- return;
27
- controller.abort();
28
- port.postMessage({ type: "unsubscribe" });
29
- port.close();
30
- };
31
- }
32
- function accept(event, producer) {
33
- event.stopPropagation();
34
- const port = event.detail.port;
35
- let alive = true;
36
- const respond = (value, transfer) => {
37
- if (!alive)
38
- return;
39
- port.postMessage({ type: "change", value }, transfer ?? []);
40
- };
41
- let teardown;
42
- try {
43
- teardown = producer(respond);
44
- } catch (err) {
45
- console.error("[patchwork-providers] subscribe producer threw:", err);
46
- }
47
- const stop = () => {
48
- if (!alive)
49
- return;
50
- alive = false;
51
- try {
52
- teardown?.();
53
- } catch (err) {
54
- console.error("[patchwork-providers] subscribe teardown threw:", err);
55
- }
56
- port.close();
57
- };
58
- port.onmessage = (event2) => {
59
- if (event2.data?.type === "unsubscribe")
60
- stop();
61
- };
62
- }
63
- export {
64
- accept as a,
65
- subscribe as s
66
- };
67
- //# sourceMappingURL=index-FKZtof6Z.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"index-FKZtof6Z.js","sources":["../../node_modules/.pnpm/@inkandswitch+patchwork-providers@0.5.1_@automerge+automerge-repo@2.6.0-subduction.47_supports-color@7.2.0_/node_modules/@inkandswitch/patchwork-providers/dist/index.js"],"sourcesContent":["const viewTags = new Set([\"patchwork-view\"]);\nexport function registerPatchworkViewTag(tagName) {\n viewTags.add(tagName.toLowerCase());\n}\n/**\n * Open a streaming subscription. Dispatches a `patchwork:subscribe` for the\n * given `selector` carrying a fresh `MessageChannel` port; the answering\n * provider pushes values back over that channel for as long as the\n * subscription is live. `listener` is invoked once per emission (the first\n * delivery is always async because `MessagePort.postMessage` queues a task).\n *\n * The event is dispatched from the nearest enclosing `<patchwork-view>` so\n * callers can pass any node inside a provider subtree. An unclaimed\n * subscription is never settled: if no provider answers, `listener` simply\n * never fires.\n *\n * Returns an unsubscribe function. Calling it tells the provider to tear down\n * (via an `unsubscribe` message) and closes the consumer's port; any values\n * the provider emits after that are dropped.\n */\nexport function subscribe(element, selector, listener) {\n const view = element.closest([...viewTags].join(\",\"));\n const dispatchEl = view ?? element;\n const channel = new MessageChannel();\n const port = channel.port2;\n const controller = new AbortController();\n const { signal } = controller;\n port.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"change\")\n listener(event.data.value);\n }, { signal });\n // addEventListener (unlike assigning onmessage) does not implicitly start\n // the port, so message delivery has to be kicked off explicitly.\n port.start();\n const detail = {\n selector,\n port: channel.port1,\n };\n dispatchEl.dispatchEvent(new CustomEvent(\"patchwork:subscribe\", {\n detail,\n bubbles: true,\n composed: true,\n }));\n return () => {\n if (signal.aborted)\n return;\n controller.abort();\n port.postMessage({ type: \"unsubscribe\" });\n port.close();\n };\n}\n/**\n * Answer a `patchwork:subscribe`. The `producer` receives a `respond`\n * callback it can call any number of times to push values to the consumer,\n * and may return a teardown that runs when the consumer unsubscribes. Stops\n * propagation so ancestor providers don't double-answer. Values emitted after\n * the consumer unsubscribes are dropped.\n *\n * `respond` takes an optional list of `Transferable`s as a second argument;\n * they are handed to `MessagePort.postMessage` so the named objects are moved\n * to the consumer instead of copied. Use this when the value carries something\n * that can't be (or is expensive to) structured-clone, e.g. a `MessagePort`\n * or a large `ArrayBuffer`. `T` is left unconstrained for this reason — once\n * you transfer, the value is no longer plain JSON.\n */\nexport function accept(event, producer) {\n event.stopPropagation();\n const port = event.detail.port;\n let alive = true;\n const respond = (value, transfer) => {\n if (!alive)\n return;\n port.postMessage({ type: \"change\", value }, transfer ?? []);\n };\n let teardown;\n try {\n teardown = producer(respond);\n }\n catch (err) {\n console.error(\"[patchwork-providers] subscribe producer threw:\", err);\n }\n const stop = () => {\n if (!alive)\n return;\n alive = false;\n try {\n teardown?.();\n }\n catch (err) {\n console.error(\"[patchwork-providers] subscribe teardown threw:\", err);\n }\n port.close();\n };\n port.onmessage = (event) => {\n if (event.data?.type === \"unsubscribe\")\n stop();\n };\n}\n/**\n * One-shot convenience wrapper over {@link subscribe}: opens a subscription\n * for `selector`, resolves with the first value a provider emits, then\n * immediately unsubscribes. Because there is no fallback provider, an\n * unclaimed selector never resolves — use `subscribe` directly if you need to\n * handle the \"no provider\" case.\n */\nexport function request(element, selector) {\n return new Promise((resolve) => {\n const unsubscribe = subscribe(element, selector, (value) => {\n // The first emission is always async (postMessage queues a task), so\n // `unsubscribe` is assigned by the time this fires.\n unsubscribe();\n resolve(value);\n });\n });\n}\nexport { registerRepoProviderElement, } from \"./repo-provider.js\";\nexport { OverlayHandle } from \"./overlay-handle.js\";\nexport { OverlayRepo } from \"./overlay-repo.js\";\n"],"names":["event"],"mappings":";AAAA,MAAM,WAAW,oBAAI,IAAI,CAAC,gBAAgB,CAAC;AAoBpC,SAAS,UAAU,SAAS,UAAU,UAAU;AACnD,QAAM,OAAO,QAAQ,QAAQ,CAAC,GAAG,QAAQ,EAAE,KAAK,GAAG,CAAC;AACpD,QAAM,aAAa,QAAQ;AAC3B,QAAM,UAAU,IAAI,eAAc;AAClC,QAAM,OAAO,QAAQ;AACrB,QAAM,aAAa,IAAI,gBAAe;AACtC,QAAM,EAAE,OAAM,IAAK;AACnB,OAAK,iBAAiB,WAAW,CAAC,UAAU;AACxC,QAAI,MAAM,MAAM,SAAS;AACrB,eAAS,MAAM,KAAK,KAAK;AAAA,EACjC,GAAG,EAAE,QAAQ;AAGb,OAAK,MAAK;AACV,QAAM,SAAS;AAAA,IACX;AAAA,IACA,MAAM,QAAQ;AAAA,EACtB;AACI,aAAW,cAAc,IAAI,YAAY,uBAAuB;AAAA,IAC5D;AAAA,IACA,SAAS;AAAA,IACT,UAAU;AAAA,EAClB,CAAK,CAAC;AACF,SAAO,MAAM;AACT,QAAI,OAAO;AACP;AACJ,eAAW,MAAK;AAChB,SAAK,YAAY,EAAE,MAAM,cAAa,CAAE;AACxC,SAAK,MAAK;AAAA,EACd;AACJ;AAeO,SAAS,OAAO,OAAO,UAAU;AACpC,QAAM,gBAAe;AACrB,QAAM,OAAO,MAAM,OAAO;AAC1B,MAAI,QAAQ;AACZ,QAAM,UAAU,CAAC,OAAO,aAAa;AACjC,QAAI,CAAC;AACD;AACJ,SAAK,YAAY,EAAE,MAAM,UAAU,MAAK,GAAI,YAAY,EAAE;AAAA,EAC9D;AACA,MAAI;AACJ,MAAI;AACA,eAAW,SAAS,OAAO;AAAA,EAC/B,SACO,KAAK;AACR,YAAQ,MAAM,mDAAmD,GAAG;AAAA,EACxE;AACA,QAAM,OAAO,MAAM;AACf,QAAI,CAAC;AACD;AACJ,YAAQ;AACR,QAAI;AACA,iBAAQ;AAAA,IACZ,SACO,KAAK;AACR,cAAQ,MAAM,mDAAmD,GAAG;AAAA,IACxE;AACA,SAAK,MAAK;AAAA,EACd;AACA,OAAK,YAAY,CAACA,WAAU;AACxB,QAAIA,OAAM,MAAM,SAAS;AACrB,WAAI;AAAA,EACZ;AACJ;","x_google_ignoreList":[0]}