@inkandswitch/patchwork-pkg-base 0.0.38 → 0.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.38",
3
+ "version": "0.0.39",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -1,6 +1,7 @@
1
1
  import { isValidAutomergeUrl, encodeHeads } from "@automerge/automerge-repo/slim";
2
- import { a as accept } from "./index-DAqs20jZ.js";
2
+ import { a as accept } from "./index-FKZtof6Z.js";
3
3
  import { c as canonicalUrl, S as SKIPPED_DATATYPES } from "./clone-policy-C4pVv5sr.js";
4
+ import "@automerge/automerge-repo";
4
5
  const ROOT_DOC_SELECTOR = "draft:root-doc";
5
6
  const CHECKED_OUT_SELECTOR = "draft:checked-out";
6
7
  const DRAFT_LIST_SELECTOR = "draft:list";
@@ -359,4 +360,4 @@ function sameHeads(a, b) {
359
360
  export {
360
361
  DraftListProvider
361
362
  };
362
- //# sourceMappingURL=DraftListProvider-D3dHIGmO.js.map
363
+ //# sourceMappingURL=DraftListProvider-D41q9O5B.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DraftListProvider-D3dHIGmO.js","sources":["../../src/providers/DraftListProvider.ts"],"sourcesContent":["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\";\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 DraftListProvider = (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-list-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-list 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 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` when pinned and the checked-out draft's\n // fork point otherwise (see `currentBaseline`). Re-emitted whenever the\n // checkout doc or a tracked draft's clones change.\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: { url: docUrl, members: [], childCount: 0, name: null },\n drafts: [],\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. A new\n // clone also gives a checked-out draft a fork-point baseline, so re-publish.\n const onTrackedChange = () => {\n scheduleRewalk();\n recomputeList();\n notifyBaselines();\n };\n const onHostDocChange = () => scheduleRewalk();\n // The checkout doc changed: its `at` checkpoint may have been set, cleared, or\n // moved, so re-publish every live `draft:baseline` subscriber.\n const onCheckedOutChange = () => {\n notifyBaselines();\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 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 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 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-list 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 pinned checkpoint's `from` or the checked-out draft's fork\n // point for `target` (see `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 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 const liveCheckedOut = checkedOutHandle;\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\n // Reconcile the checkout pointer: if the checked-out draft is gone\n // (merged or detached), fall back to main.\n const selected = liveCheckedOut.doc()?.checkedOut ?? null;\n if (selected && !allDrafts.includes(selected)) {\n liveCheckedOut.change((d) => {\n d.checkedOut = null;\n });\n }\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.\n function recomputeList(): void {\n if (disposed) return;\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 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 members: clonesToMembers(doc.clones),\n childCount: doc.drafts.length,\n name: doc.name ?? null,\n });\n }\n return { main: computeMainSummary(), drafts };\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\n const mainClones = mainDraftHandle?.doc()?.clones;\n if (mainClones && Object.keys(mainClones).length > 0) {\n return { url, members: clonesToMembers(mainClones), childCount, name };\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, members, childCount, name };\n }\n\n // The diff baseline for `target`, authoritative for both main and drafts:\n // - a pinned checkpoint honors that doc's `from` exactly (`null` = no diff);\n // - otherwise, on a draft, the doc diffs against its clone's fork point;\n // - otherwise (main, no pin) there is no baseline.\n function currentBaseline(target: AutomergeUrl): Baseline {\n const doc = checkedOutHandle?.doc();\n const entry = doc?.at?.[target];\n if (entry) return { heads: entry.from ?? null };\n\n const checkedOut = doc?.checkedOut;\n if (checkedOut) {\n const clonedAt = trackedDrafts.get(checkedOut)?.doc()?.clones?.[target]\n ?.clonedAt;\n return { heads: clonedAt ?? null };\n }\n return { heads: 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\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 ?? [];\n for (const child of drafts) {\n if (isValidAutomergeUrl(child)) queue.push(child);\n }\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 (!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.childCount === b.childCount &&\n a.name === b.name &&\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
+ {"version":3,"file":"DraftListProvider-D41q9O5B.js","sources":["../../src/providers/DraftListProvider.ts"],"sourcesContent":["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\";\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 DraftListProvider = (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-list-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-list 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 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` when pinned and the checked-out draft's\n // fork point otherwise (see `currentBaseline`). Re-emitted whenever the\n // checkout doc or a tracked draft's clones change.\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: { url: docUrl, members: [], childCount: 0, name: null },\n drafts: [],\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. A new\n // clone also gives a checked-out draft a fork-point baseline, so re-publish.\n const onTrackedChange = () => {\n scheduleRewalk();\n recomputeList();\n notifyBaselines();\n };\n const onHostDocChange = () => scheduleRewalk();\n // The checkout doc changed: its `at` checkpoint may have been set, cleared, or\n // moved, so re-publish every live `draft:baseline` subscriber.\n const onCheckedOutChange = () => {\n notifyBaselines();\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 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 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 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-list 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 pinned checkpoint's `from` or the checked-out draft's fork\n // point for `target` (see `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 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 const liveCheckedOut = checkedOutHandle;\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\n // Reconcile the checkout pointer: if the checked-out draft is gone\n // (merged or detached), fall back to main.\n const selected = liveCheckedOut.doc()?.checkedOut ?? null;\n if (selected && !allDrafts.includes(selected)) {\n liveCheckedOut.change((d) => {\n d.checkedOut = null;\n });\n }\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.\n function recomputeList(): void {\n if (disposed) return;\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 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 members: clonesToMembers(doc.clones),\n childCount: doc.drafts.length,\n name: doc.name ?? null,\n });\n }\n return { main: computeMainSummary(), drafts };\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\n const mainClones = mainDraftHandle?.doc()?.clones;\n if (mainClones && Object.keys(mainClones).length > 0) {\n return { url, members: clonesToMembers(mainClones), childCount, name };\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, members, childCount, name };\n }\n\n // The diff baseline for `target`, authoritative for both main and drafts:\n // - a pinned checkpoint honors that doc's `from` exactly (`null` = no diff);\n // - otherwise, on a draft, the doc diffs against its clone's fork point;\n // - otherwise (main, no pin) there is no baseline.\n function currentBaseline(target: AutomergeUrl): Baseline {\n const doc = checkedOutHandle?.doc();\n const entry = doc?.at?.[target];\n if (entry) return { heads: entry.from ?? null };\n\n const checkedOut = doc?.checkedOut;\n if (checkedOut) {\n const clonedAt = trackedDrafts.get(checkedOut)?.doc()?.clones?.[target]\n ?.clonedAt;\n return { heads: clonedAt ?? null };\n }\n return { heads: 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\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 ?? [];\n for (const child of drafts) {\n if (isValidAutomergeUrl(child)) queue.push(child);\n }\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 (!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.childCount === b.childCount &&\n a.name === b.name &&\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,6 +1,7 @@
1
1
  import { isValidAutomergeUrl, stringifyAutomergeUrl, parseAutomergeUrl } from "@automerge/automerge-repo/slim";
2
- import { s as subscribe, a as accept } from "./index-DAqs20jZ.js";
2
+ import { s as subscribe, a as accept } from "./index-FKZtof6Z.js";
3
3
  import { c as canonicalUrl, S as SKIPPED_DATATYPES } from "./clone-policy-C4pVv5sr.js";
4
+ import "@automerge/automerge-repo";
4
5
  const HANDLE_DESCRIPTOR_SELECTOR = "repo:handle-descriptor";
5
6
  const CHECKED_OUT_SELECTOR = "draft:checked-out";
6
7
  const DraftOverlayProvider = (element) => {
@@ -120,4 +121,4 @@ const DraftOverlayProvider = (element) => {
120
121
  export {
121
122
  DraftOverlayProvider
122
123
  };
123
- //# sourceMappingURL=DraftOverlayProvider-Cae4SJD7.js.map
124
+ //# sourceMappingURL=DraftOverlayProvider-B0Mtq1UF.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DraftOverlayProvider-Cae4SJD7.js","sources":["../../src/providers/DraftOverlayProvider.ts"],"sourcesContent":["import {\n isValidAutomergeUrl,\n parseAutomergeUrl,\n stringifyAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type UrlHeads,\n} from \"@automerge/automerge-repo/slim\";\nimport {\n accept,\n subscribe,\n type SubscribeEvent,\n} from \"@inkandswitch/patchwork-providers\";\n\n// Shape of a `repo:handle-descriptor` answer. The one-shot host's\n// patchwork-providers (0.2.2) does not export this type, so it is declared\n// locally; it must stay in sync with the host's `OverlayRepo`.\ntype DocHandleDescriptor = {\n url: AutomergeUrl;\n cloneUrl?: AutomergeUrl;\n};\n\nimport type { CheckedOutDraft, DraftDoc } from \"../draft-types.js\";\nimport { SKIPPED_DATATYPES, canonicalUrl } from \"../clone-policy.js\";\n\nconst HANDLE_DESCRIPTOR_SELECTOR = \"repo:handle-descriptor\";\nconst CHECKED_OUT_SELECTOR = \"draft:checked-out\";\n\n// Mounts on a draft URL and remaps documents resolved beneath it onto\n// per-draft clones, so edits stay inside the draft.\n//\n// Under the new provider model the host `<patchwork-view>` wraps legacy tool\n// document resolution in an `OverlayRepo` that asks the provider tree for a\n// `repo:handle-descriptor` descriptor. This provider answers with\n// `{ url, cloneUrl }`: the clone is forked eagerly the first time a document is\n// requested in this draft (recorded in `DraftDoc.clones`), and the editor then\n// reads and writes the clone while still reporting the original url. The fork\n// point lives in `DraftDoc.clones[url].clonedAt`; the draft-list provider reads\n// it to serve `draft:baseline` (this provider no longer answers that).\n//\n// On \"main\" (empty `url`) there is no clone remapping, but the provider still\n// answers `repo:handle-descriptor` so that an active checkpoint can pin nested\n// docs: it returns the original url carrying the checkpoint's `to` heads when one\n// is set. Pinning also applies on a draft, baked onto the clone url. The per-doc\n// `to`/`from` heads live on the checked-out draft (`CheckedOutDraft.at`), read\n// here via the `draft:checked-out` subscription. A non-empty but invalid `url`\n// is a misconfiguration and disables the provider.\nexport const DraftOverlayProvider = (element: HTMLElement) => {\n const rawUrl = element.getAttribute(\"url\");\n // Empty `url` = \"main\": no clone remapping, but we still answer\n // `repo:handle-descriptor` so an active checkpoint can pin nested docs.\n let draftUrl: AutomergeUrl | null = null;\n if (rawUrl) {\n if (!isValidAutomergeUrl(rawUrl)) {\n console.warn(\n `[drafts] <patchwork-view component=\"patchwork-draft-overlay-provider\"> ` +\n `has an invalid url attribute (got ${JSON.stringify(rawUrl)})`\n );\n return () => {};\n }\n draftUrl = rawUrl;\n }\n\n const repo = \"repo\" in window ? window.repo : undefined;\n if (!repo) {\n console.warn(\n \"[drafts] window.repo is not set; draft overlay provider disabled\"\n );\n return () => {};\n }\n const liveRepo = repo;\n\n let disposed = false;\n\n // One eager-clone resolution per original url; de-dupes concurrent requests.\n const cloneResolutions = new Map<AutomergeUrl, Promise<AutomergeUrl>>();\n\n // The draft doc backing this overlay (clone bookkeeping). Null on \"main\".\n const ready: Promise<DocHandle<DraftDoc>> | null = draftUrl\n ? (async () => {\n const handle = await liveRepo.find<DraftDoc>(draftUrl);\n if (disposed) throw new Error(\"[drafts] provider disposed mid-load\");\n return handle;\n })()\n : null;\n ready?.catch((err) => {\n console.error(`[drafts] failed to load draft overlay for ${draftUrl}:`, err);\n });\n\n // Track the checked-out draft's checkpoint so descriptors can pin a nested doc\n // to its per-doc `to` heads. The url is served by the ancestor draft-list\n // provider; we read `at[original].to` live at resolve time (one-shot, so we\n // never block on this — absent means render live).\n let checkedOutHandle: DocHandle<CheckedOutDraft> | null = null;\n const unsubscribeCheckedOut = subscribe<AutomergeUrl>(\n element,\n { type: CHECKED_OUT_SELECTOR },\n (url) => {\n if (disposed || !isValidAutomergeUrl(url)) return;\n void liveRepo.find<CheckedOutDraft>(url).then((handle) => {\n if (!disposed) checkedOutHandle = handle;\n });\n }\n );\n\n const onSubscribe = (event: SubscribeEvent) => {\n const selector = event.detail.selector;\n\n if (selector.type === HANDLE_DESCRIPTOR_SELECTOR) {\n const rawTarget = selector.url;\n if (typeof rawTarget !== \"string\" || !isValidAutomergeUrl(rawTarget)) {\n return;\n }\n const original = canonicalUrl(rawTarget);\n accept<DocHandleDescriptor>(event, (respond) => {\n void resolveDescriptor(original).then((descriptor) => {\n if (disposed) return;\n respond(descriptor);\n });\n });\n return;\n }\n };\n\n element.addEventListener(\"patchwork:subscribe\", onSubscribe);\n return () => {\n disposed = true;\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n unsubscribeCheckedOut();\n cloneResolutions.clear();\n };\n\n // Resolve a `repo:handle-descriptor` request. The backing url is pinned to the\n // active checkpoint's `to` heads for this doc (if any) so nested views freeze\n // with the doc they live in; `OverlayRepo` honors heads on the backing url.\n // - On \"main\" (no draft): no clone, just the (maybe pinned) original.\n // - Skipped docs (account, contacts): the real doc, never forked.\n // - Everything else on a draft: the per-draft clone (pinned when checked out).\n async function resolveDescriptor(\n original: AutomergeUrl\n ): Promise<DocHandleDescriptor> {\n const to = checkedOutHandle?.doc()?.at?.[original]?.to ?? undefined;\n if (!draftUrl || (await isSkippedDoc(original))) {\n return to\n ? { url: original, cloneUrl: withHeads(original, to) }\n : { url: original };\n }\n const cloneUrl = await resolveClone(original);\n return { url: original, cloneUrl: withHeads(cloneUrl, to) };\n }\n\n // Stamp `heads` onto `url` (same documentId), or return it unchanged when\n // there is no pin. Heads ride on the url so `OverlayRepo` resolves the doc at\n // that point in time.\n function withHeads(\n url: AutomergeUrl,\n heads: UrlHeads | undefined\n ): AutomergeUrl {\n if (!heads) return url;\n return stringifyAutomergeUrl({\n documentId: parseAutomergeUrl(url).documentId,\n heads,\n });\n }\n\n // A doc is skipped when its `@patchwork.type` is in `SKIPPED_DATATYPES`. On\n // any failure we fall back to cloning (the existing behaviour), which is the\n // safe default — a doc that should be skipped merely keeps forking, it isn't\n // lost.\n async function isSkippedDoc(original: AutomergeUrl): Promise<boolean> {\n try {\n const handle = await liveRepo.find<{ \"@patchwork\"?: { type?: string } }>(\n original\n );\n const type = handle.doc()?.[\"@patchwork\"]?.type;\n return type != null && SKIPPED_DATATYPES.has(type);\n } catch {\n return false;\n }\n }\n\n // Ensure a clone of `original` exists for this draft and return its url.\n // Reuses an existing clone recorded in `DraftDoc.clones`; otherwise forks\n // `original` at its current heads and records the fork point so the baseline\n // and merge-back can find it.\n function resolveClone(original: AutomergeUrl): Promise<AutomergeUrl> {\n const cached = cloneResolutions.get(original);\n if (cached) return cached;\n const promise = (async () => {\n if (!ready) throw new Error(\"[drafts] resolveClone called without a draft\");\n const handle = await ready;\n const existing = handle.doc()?.clones?.[original];\n if (existing) return canonicalUrl(existing.cloneUrl);\n\n const originalHandle = await liveRepo.find<unknown>(original);\n const clonedAt = originalHandle.heads();\n const clone = liveRepo.clone(originalHandle);\n const cloneUrl = canonicalUrl(clone.url);\n\n handle.change((d) => {\n d.clones[original] = { cloneUrl, clonedAt };\n });\n\n return cloneUrl;\n })();\n cloneResolutions.set(original, promise);\n return promise;\n }\n};\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DraftOverlayProvider-B0Mtq1UF.js","sources":["../../src/providers/DraftOverlayProvider.ts"],"sourcesContent":["import {\n isValidAutomergeUrl,\n parseAutomergeUrl,\n stringifyAutomergeUrl,\n type AutomergeUrl,\n type DocHandle,\n type UrlHeads,\n} from \"@automerge/automerge-repo/slim\";\nimport {\n accept,\n subscribe,\n type SubscribeEvent,\n} from \"@inkandswitch/patchwork-providers\";\n\n// Shape of a `repo:handle-descriptor` answer. The one-shot host's\n// patchwork-providers (0.2.2) does not export this type, so it is declared\n// locally; it must stay in sync with the host's `OverlayRepo`.\ntype DocHandleDescriptor = {\n url: AutomergeUrl;\n cloneUrl?: AutomergeUrl;\n};\n\nimport type { CheckedOutDraft, DraftDoc } from \"../draft-types.js\";\nimport { SKIPPED_DATATYPES, canonicalUrl } from \"../clone-policy.js\";\n\nconst HANDLE_DESCRIPTOR_SELECTOR = \"repo:handle-descriptor\";\nconst CHECKED_OUT_SELECTOR = \"draft:checked-out\";\n\n// Mounts on a draft URL and remaps documents resolved beneath it onto\n// per-draft clones, so edits stay inside the draft.\n//\n// Under the new provider model the host `<patchwork-view>` wraps legacy tool\n// document resolution in an `OverlayRepo` that asks the provider tree for a\n// `repo:handle-descriptor` descriptor. This provider answers with\n// `{ url, cloneUrl }`: the clone is forked eagerly the first time a document is\n// requested in this draft (recorded in `DraftDoc.clones`), and the editor then\n// reads and writes the clone while still reporting the original url. The fork\n// point lives in `DraftDoc.clones[url].clonedAt`; the draft-list provider reads\n// it to serve `draft:baseline` (this provider no longer answers that).\n//\n// On \"main\" (empty `url`) there is no clone remapping, but the provider still\n// answers `repo:handle-descriptor` so that an active checkpoint can pin nested\n// docs: it returns the original url carrying the checkpoint's `to` heads when one\n// is set. Pinning also applies on a draft, baked onto the clone url. The per-doc\n// `to`/`from` heads live on the checked-out draft (`CheckedOutDraft.at`), read\n// here via the `draft:checked-out` subscription. A non-empty but invalid `url`\n// is a misconfiguration and disables the provider.\nexport const DraftOverlayProvider = (element: HTMLElement) => {\n const rawUrl = element.getAttribute(\"url\");\n // Empty `url` = \"main\": no clone remapping, but we still answer\n // `repo:handle-descriptor` so an active checkpoint can pin nested docs.\n let draftUrl: AutomergeUrl | null = null;\n if (rawUrl) {\n if (!isValidAutomergeUrl(rawUrl)) {\n console.warn(\n `[drafts] <patchwork-view component=\"patchwork-draft-overlay-provider\"> ` +\n `has an invalid url attribute (got ${JSON.stringify(rawUrl)})`\n );\n return () => {};\n }\n draftUrl = rawUrl;\n }\n\n const repo = \"repo\" in window ? window.repo : undefined;\n if (!repo) {\n console.warn(\n \"[drafts] window.repo is not set; draft overlay provider disabled\"\n );\n return () => {};\n }\n const liveRepo = repo;\n\n let disposed = false;\n\n // One eager-clone resolution per original url; de-dupes concurrent requests.\n const cloneResolutions = new Map<AutomergeUrl, Promise<AutomergeUrl>>();\n\n // The draft doc backing this overlay (clone bookkeeping). Null on \"main\".\n const ready: Promise<DocHandle<DraftDoc>> | null = draftUrl\n ? (async () => {\n const handle = await liveRepo.find<DraftDoc>(draftUrl);\n if (disposed) throw new Error(\"[drafts] provider disposed mid-load\");\n return handle;\n })()\n : null;\n ready?.catch((err) => {\n console.error(`[drafts] failed to load draft overlay for ${draftUrl}:`, err);\n });\n\n // Track the checked-out draft's checkpoint so descriptors can pin a nested doc\n // to its per-doc `to` heads. The url is served by the ancestor draft-list\n // provider; we read `at[original].to` live at resolve time (one-shot, so we\n // never block on this — absent means render live).\n let checkedOutHandle: DocHandle<CheckedOutDraft> | null = null;\n const unsubscribeCheckedOut = subscribe<AutomergeUrl>(\n element,\n { type: CHECKED_OUT_SELECTOR },\n (url) => {\n if (disposed || !isValidAutomergeUrl(url)) return;\n void liveRepo.find<CheckedOutDraft>(url).then((handle) => {\n if (!disposed) checkedOutHandle = handle;\n });\n }\n );\n\n const onSubscribe = (event: SubscribeEvent) => {\n const selector = event.detail.selector;\n\n if (selector.type === HANDLE_DESCRIPTOR_SELECTOR) {\n const rawTarget = selector.url;\n if (typeof rawTarget !== \"string\" || !isValidAutomergeUrl(rawTarget)) {\n return;\n }\n const original = canonicalUrl(rawTarget);\n accept<DocHandleDescriptor>(event, (respond) => {\n void resolveDescriptor(original).then((descriptor) => {\n if (disposed) return;\n respond(descriptor);\n });\n });\n return;\n }\n };\n\n element.addEventListener(\"patchwork:subscribe\", onSubscribe);\n return () => {\n disposed = true;\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n unsubscribeCheckedOut();\n cloneResolutions.clear();\n };\n\n // Resolve a `repo:handle-descriptor` request. The backing url is pinned to the\n // active checkpoint's `to` heads for this doc (if any) so nested views freeze\n // with the doc they live in; `OverlayRepo` honors heads on the backing url.\n // - On \"main\" (no draft): no clone, just the (maybe pinned) original.\n // - Skipped docs (account, contacts): the real doc, never forked.\n // - Everything else on a draft: the per-draft clone (pinned when checked out).\n async function resolveDescriptor(\n original: AutomergeUrl\n ): Promise<DocHandleDescriptor> {\n const to = checkedOutHandle?.doc()?.at?.[original]?.to ?? undefined;\n if (!draftUrl || (await isSkippedDoc(original))) {\n return to\n ? { url: original, cloneUrl: withHeads(original, to) }\n : { url: original };\n }\n const cloneUrl = await resolveClone(original);\n return { url: original, cloneUrl: withHeads(cloneUrl, to) };\n }\n\n // Stamp `heads` onto `url` (same documentId), or return it unchanged when\n // there is no pin. Heads ride on the url so `OverlayRepo` resolves the doc at\n // that point in time.\n function withHeads(\n url: AutomergeUrl,\n heads: UrlHeads | undefined\n ): AutomergeUrl {\n if (!heads) return url;\n return stringifyAutomergeUrl({\n documentId: parseAutomergeUrl(url).documentId,\n heads,\n });\n }\n\n // A doc is skipped when its `@patchwork.type` is in `SKIPPED_DATATYPES`. On\n // any failure we fall back to cloning (the existing behaviour), which is the\n // safe default — a doc that should be skipped merely keeps forking, it isn't\n // lost.\n async function isSkippedDoc(original: AutomergeUrl): Promise<boolean> {\n try {\n const handle = await liveRepo.find<{ \"@patchwork\"?: { type?: string } }>(\n original\n );\n const type = handle.doc()?.[\"@patchwork\"]?.type;\n return type != null && SKIPPED_DATATYPES.has(type);\n } catch {\n return false;\n }\n }\n\n // Ensure a clone of `original` exists for this draft and return its url.\n // Reuses an existing clone recorded in `DraftDoc.clones`; otherwise forks\n // `original` at its current heads and records the fork point so the baseline\n // and merge-back can find it.\n function resolveClone(original: AutomergeUrl): Promise<AutomergeUrl> {\n const cached = cloneResolutions.get(original);\n if (cached) return cached;\n const promise = (async () => {\n if (!ready) throw new Error(\"[drafts] resolveClone called without a draft\");\n const handle = await ready;\n const existing = handle.doc()?.clones?.[original];\n if (existing) return canonicalUrl(existing.cloneUrl);\n\n const originalHandle = await liveRepo.find<unknown>(original);\n const clonedAt = originalHandle.heads();\n const clone = liveRepo.clone(originalHandle);\n const cloneUrl = canonicalUrl(clone.url);\n\n handle.change((d) => {\n d.clones[original] = { cloneUrl, clonedAt };\n });\n\n return cloneUrl;\n })();\n cloneResolutions.set(original, promise);\n return promise;\n }\n};\n"],"names":[],"mappings":"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"}
@@ -1,5 +1,7 @@
1
+ import "@automerge/automerge-repo";
2
+ const viewTags = /* @__PURE__ */ new Set(["patchwork-view"]);
1
3
  function subscribe(element, selector, listener) {
2
- const view = element.closest("patchwork-view");
4
+ const view = element.closest([...viewTags].join(","));
3
5
  const dispatchEl = view ?? element;
4
6
  const channel = new MessageChannel();
5
7
  const port = channel.port2;
@@ -31,10 +33,10 @@ function accept(event, producer) {
31
33
  event.stopPropagation();
32
34
  const port = event.detail.port;
33
35
  let alive = true;
34
- const respond = (value) => {
36
+ const respond = (value, transfer) => {
35
37
  if (!alive)
36
38
  return;
37
- port.postMessage({ type: "change", value });
39
+ port.postMessage({ type: "change", value }, transfer ?? []);
38
40
  };
39
41
  let teardown;
40
42
  try {
@@ -62,4 +64,4 @@ export {
62
64
  accept as a,
63
65
  subscribe as s
64
66
  };
65
- //# sourceMappingURL=index-DAqs20jZ.js.map
67
+ //# sourceMappingURL=index-FKZtof6Z.js.map
@@ -0,0 +1 @@
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]}