@inkandswitch/patchwork-pkg-base 0.0.65 → 0.0.67

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/module-settings-url +1 -0
  2. package/package.json +2 -1
  3. package/packages/codemirror-markdown/example.js +1 -2
  4. package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js → DraftOverlayProvider-D2vIz7hy.js} +2 -3
  5. package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js.map → DraftOverlayProvider-D2vIz7hy.js.map} +1 -1
  6. package/packages/drafts/dist/assets/{DraftStateProvider-DX8b_RgQ.js → DraftStateProvider-Bhz0S4c1.js} +212 -87
  7. package/packages/drafts/dist/assets/DraftStateProvider-Bhz0S4c1.js.map +1 -0
  8. package/packages/drafts/dist/assets/{draft-docs-Bi8K1y3a.js → draft-docs-DyJ2l14d.js} +108 -99
  9. package/packages/drafts/dist/assets/draft-docs-DyJ2l14d.js.map +1 -0
  10. package/packages/drafts/dist/assets/{main-EEciTwbZ.js → main-CG9HOCai.js} +14 -10
  11. package/packages/drafts/dist/assets/main-CG9HOCai.js.map +1 -0
  12. package/packages/drafts/dist/index.js +9 -9
  13. package/packages/drafts/package.json +5 -5
  14. package/packages/sideboard/dist/{chunk-KHS2XJKP.js → chunk-6POQCUBB.js} +3 -3
  15. package/packages/sideboard/dist/{chunk-TXFTRSLY.js → chunk-DS3QVJZT.js} +3 -3
  16. package/packages/sideboard/dist/chunk-DS3QVJZT.js.map +7 -0
  17. package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js → create-new-menu-VA4ELRJ6.js} +2 -2
  18. package/packages/sideboard/dist/document-list-panel-GHYN6JR5.js +2 -0
  19. package/packages/sideboard/dist/index.js +1 -1
  20. package/packages/sideboard/dist/sideboard/sideboard.js +1 -1
  21. package/packages/tldraw4/dist/{chunk-IKZWERSR.js → chunk-G3PMV62Z.js} +1 -3
  22. package/packages/tldraw4/dist/{chunk-7CJQMVDI.js → chunk-PMHAL5YM.js} +4 -4
  23. package/packages/tldraw4/dist/datatype.js +2 -2
  24. package/packages/tldraw4/dist/main.js +1 -1
  25. package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js → sanitizeSvg-NCGPTJ3E.js} +2 -2
  26. package/packages/tldraw4/dist/tool.js +450 -5675
  27. package/packages/tldraw4/dist/tool.js.map +4 -4
  28. package/packages/tldraw4/package.json +0 -1
  29. package/packages/tldraw5/dist/{chunk-GKLJTYCF.js → chunk-DO47QRII.js} +1 -3
  30. package/packages/tldraw5/dist/{chunk-4JN3PIR7.js → chunk-FTKY6DDS.js} +3 -3
  31. package/packages/tldraw5/dist/{chunk-JDHJ334K.js → chunk-SZI4VCOI.js} +4 -4
  32. package/packages/tldraw5/dist/datatype.js +3 -3
  33. package/packages/tldraw5/dist/main.js +1 -1
  34. package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js → sanitizeSvg-J7F3MQGT.js} +2 -2
  35. package/packages/tldraw5/dist/{script-VW3AAU5T.js → script-4RKE5JKT.js} +3 -3
  36. package/packages/tldraw5/dist/script-4RKE5JKT.js.map +7 -0
  37. package/packages/tldraw5/dist/{tldr-archive-WERHN3T4.js → tldr-archive-5X7EMHWZ.js} +3 -3
  38. package/packages/tldraw5/dist/tool.js +293 -1543
  39. package/packages/tldraw5/dist/tool.js.map +4 -4
  40. package/packages/tldraw5/package.json +0 -1
  41. package/plugin-registry-url +1 -0
  42. package/packages/drafts/dist/assets/DraftStateProvider-DX8b_RgQ.js.map +0 -1
  43. package/packages/drafts/dist/assets/draft-docs-Bi8K1y3a.js.map +0 -1
  44. package/packages/drafts/dist/assets/index-FKZtof6Z.js +0 -67
  45. package/packages/drafts/dist/assets/index-FKZtof6Z.js.map +0 -1
  46. package/packages/drafts/dist/assets/main-EEciTwbZ.js.map +0 -1
  47. package/packages/sideboard/dist/chunk-TXFTRSLY.js.map +0 -7
  48. package/packages/sideboard/dist/document-list-panel-WDIVYTZU.js +0 -2
  49. package/packages/tldraw5/dist/script-VW3AAU5T.js.map +0 -7
  50. /package/packages/sideboard/dist/{chunk-KHS2XJKP.js.map → chunk-6POQCUBB.js.map} +0 -0
  51. /package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js.map → create-new-menu-VA4ELRJ6.js.map} +0 -0
  52. /package/packages/sideboard/dist/{document-list-panel-WDIVYTZU.js.map → document-list-panel-GHYN6JR5.js.map} +0 -0
  53. /package/packages/tldraw4/dist/{chunk-IKZWERSR.js.map → chunk-G3PMV62Z.js.map} +0 -0
  54. /package/packages/tldraw4/dist/{chunk-7CJQMVDI.js.map → chunk-PMHAL5YM.js.map} +0 -0
  55. /package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js.map → sanitizeSvg-NCGPTJ3E.js.map} +0 -0
  56. /package/packages/tldraw5/dist/{chunk-GKLJTYCF.js.map → chunk-DO47QRII.js.map} +0 -0
  57. /package/packages/tldraw5/dist/{chunk-4JN3PIR7.js.map → chunk-FTKY6DDS.js.map} +0 -0
  58. /package/packages/tldraw5/dist/{chunk-JDHJ334K.js.map → chunk-SZI4VCOI.js.map} +0 -0
  59. /package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js.map → sanitizeSvg-J7F3MQGT.js.map} +0 -0
  60. /package/packages/tldraw5/dist/{tldr-archive-WERHN3T4.js.map → tldr-archive-5X7EMHWZ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ automerge:2rRXfQ9YHLdroyLDyWw1tcsUngBN
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inkandswitch/patchwork-pkg-base",
3
- "version": "0.0.65",
3
+ "version": "0.0.67",
4
4
  "type": "module",
5
5
  "description": "Base packages for the Patchwork system",
6
6
  "repository": {
@@ -15,6 +15,7 @@
15
15
  "build:static": "node scripts/build-static.mjs",
16
16
  "build:static:fresh": "node scripts/build-static.mjs --build",
17
17
  "build:ci": "node scripts/build-static.mjs --install",
18
+ "modules-doc": "pnpm --dir scripts/modules-doc install --frozen-lockfile && node scripts/modules-doc/index.mjs",
18
19
  "bundle": "node scripts/bundle.mjs",
19
20
  "serve:tools": "node scripts/serve-tools.mjs",
20
21
  "dev:tools": "pnpm build:static && pnpm serve:tools",
@@ -21,8 +21,7 @@ Swap out your editor: use the "Open with..." to switch to the Raw editor. It's a
21
21
 
22
22
  Don't like what you did to a document? You can use the Drafts tool in the right-hand sidebar to go back in time and make a draft at an earlier point.
23
23
 
24
- Want to invite an LLM to be a guest? Try typing ` /
25
- model` in the Watercooler sidebar and you should be able to either install a local model or paste in an OpenRouter key to chat with anything they offer. Their changes will show up in the document's history, just like yours.
24
+ Want to invite an LLM to be a guest? Try typing \`/model\` in the Watercooler sidebar and you should be able to either install a local model or paste in an OpenRouter key to chat with anything they offer. Their changes will show up in the document's history, just like yours.
26
25
 
27
26
  ## Let's make some new tools
28
27
 
@@ -1,7 +1,6 @@
1
1
  import { isValidAutomergeUrl, stringifyAutomergeUrl, parseAutomergeUrl } from "@automerge/automerge-repo/slim";
2
- import { s as subscribe, a as accept } from "./index-FKZtof6Z.js";
2
+ import { subscribe, accept } from "@inkandswitch/patchwork-providers";
3
3
  import { S as SKIPPED_DATATYPES, c as canonicalUrl } from "./clone-policy-CIdEhg7W.js";
4
- import "@automerge/automerge-repo";
5
4
  const HANDLE_DESCRIPTOR_SELECTOR = "repo:handle-descriptor";
6
5
  const CHECKED_OUT_SELECTOR = "draft:checked-out";
7
6
  const DraftOverlayProvider = (element) => {
@@ -181,4 +180,4 @@ const DraftOverlayProvider = (element) => {
181
180
  export {
182
181
  DraftOverlayProvider
183
182
  };
184
- //# sourceMappingURL=DraftOverlayProvider-VYKlzOy1.js.map
183
+ //# sourceMappingURL=DraftOverlayProvider-D2vIz7hy.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DraftOverlayProvider-VYKlzOy1.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 DocHandleDescriptor,\n type SubscribeEvent,\n} from \"@inkandswitch/patchwork-providers\";\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// Remaps documents resolved beneath it onto per-draft clones, so edits stay\n// inside the checked-out draft — and re-points *live* in place when the\n// selection changes, without the host remounting anything.\n//\n// The host `<patchwork-view>` wraps tool document resolution in an\n// `OverlayRepo` that opens a *streaming* `repo:handle-descriptor` subscription\n// per document. This provider always claims those subscriptions (even while\n// \"main\" is selected, where it answers a pass-through `{ url }`) and keeps the\n// `respond` callbacks registered. It follows the selection itself via the\n// ancestor draft-state provider's `draft:checked-out` doc: when\n// `CheckedOutDraft.checkedOut` changes, every live subscription is re-answered\n// with the new mapping — `{ url, cloneUrl }` on a draft (the clone is forked\n// eagerly on first resolution and recorded in `DraftDoc.clones`), `{ url }` on\n// main — and the `OverlayRepo` swaps handle backings in place.\n//\n// Descriptors also honor the active checkpoint: `CheckedOutDraft.at` maps each\n// member doc to per-doc `to`/`from` heads, and the `to` heads are baked onto\n// the backing url (the clone on a draft, the original on main) so nested views\n// freeze with the doc they live in; `OverlayRepo` honors heads on the backing\n// url. Checkpoint moves (history scrubbing rewrites `at` without changing\n// `checkedOut`) also re-answer every live subscription, so pins update by\n// swapping backings in place — no remount. The fork point lives in\n// `DraftDoc.clones[url].clonedAt`; the draft-state provider reads it to serve\n// `draft:baseline` (this provider no longer answers that).\n//\n// A `url` attribute, when present, seeds the initial selection. That is how\n// the chat preview iframe (see chat's `preview-frame.ts`) pins a\n// self-bootstrapped overlay to a specific draft in a realm that has no\n// draft-state provider to follow. The *current* selection is reflected onto the\n// (un-observed, so remount-free) `draft-url` attribute for outside readers.\nexport const DraftOverlayProvider = (element: HTMLElement) => {\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 // The checked-out draft this overlay currently maps onto. Null = \"main\"\n // (pass-through descriptors, save for checkpoint pinning).\n let draftUrl: AutomergeUrl | null = null;\n let draftReady: Promise<DocHandle<DraftDoc>> | null = null;\n // Aborted and replaced on every descriptor refresh (draft re-point or\n // checkpoint move), and aborted on dispose, so in-flight resolutions from a\n // superseded state can detect they lost the race and stay silent. The\n // signal only gates `respond` — it is never passed into the resolution\n // work, which is shared across batches (see `cloneResolutions`).\n let refresh = new AbortController();\n\n // One eager-clone resolution per original url; de-dupes concurrent requests.\n // Cleared on re-point (it is per-draft state).\n const cloneResolutions = new Map<AutomergeUrl, Promise<AutomergeUrl>>();\n\n // Live `repo:handle-descriptor` subscriptions, kept so a re-point can push\n // fresh descriptors to every consumer.\n type DescriptorSubscriber = {\n original: AutomergeUrl;\n respond: (descriptor: DocHandleDescriptor) => void;\n };\n const descriptorSubscribers = new Set<DescriptorSubscriber>();\n\n // Seed the selection from the `url` attribute when present (the chat\n // preview iframe mounts us with a pinned draft and no draft-state provider).\n const rawSeed = element.getAttribute(\"url\");\n if (rawSeed) {\n if (isValidAutomergeUrl(rawSeed)) {\n void applyDraft(rawSeed);\n } else {\n console.warn(\n `[drafts] <patchwork-view component=\"patchwork-draft-overlay-provider\"> ` +\n `has an invalid url attribute (got ${JSON.stringify(rawSeed)})`\n );\n }\n }\n\n // Follow the selection: the ancestor draft-state provider serves the\n // ephemeral CheckedOutDraft doc url; we watch its `checkedOut` live. The\n // same doc carries the checkpoint (`at`), read at resolve time so\n // descriptors can pin a nested doc to its per-doc `to` heads — absent means\n // render live.\n //\n // A draft switch re-points via `applyDraft` (resets per-draft state); a\n // checkpoint-only move (scrubbing rewrites `at` while `checkedOut` stays\n // put) just re-answers live subscriptions with freshly pinned descriptors.\n let checkedOutHandle: DocHandle<CheckedOutDraft> | null = null;\n let checkpointSignature = JSON.stringify(null);\n const onCheckedOutChange = () => {\n const doc = checkedOutHandle?.doc();\n const nextDraft = doc?.checkedOut ?? null;\n const nextSignature = JSON.stringify(doc?.at ?? null);\n const checkpointMoved = nextSignature !== checkpointSignature;\n checkpointSignature = nextSignature;\n if (nextDraft !== draftUrl) {\n void applyDraft(nextDraft);\n } else if (checkpointMoved) {\n refreshDescriptors();\n }\n };\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) return;\n checkedOutHandle?.off(\"change\", onCheckedOutChange);\n checkedOutHandle = handle;\n handle.on(\"change\", onCheckedOutChange);\n onCheckedOutChange();\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 const subscriber: DescriptorSubscriber = { original, respond };\n descriptorSubscribers.add(subscriber);\n const { signal } = refresh;\n void resolveDescriptor(original)\n .then((descriptor) => {\n // A re-point raced this resolution; `applyDraft`'s refresh pass\n // answers this subscriber with the new mapping instead.\n if (signal.aborted) return;\n respond(descriptor);\n })\n .catch((err) => {\n console.error(`[drafts] failed to resolve ${original}:`, err);\n });\n return () => {\n descriptorSubscribers.delete(subscriber);\n };\n });\n return;\n }\n };\n\n element.addEventListener(\"patchwork:subscribe\", onSubscribe);\n return () => {\n disposed = true;\n refresh.abort();\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n unsubscribeCheckedOut();\n checkedOutHandle?.off(\"change\", onCheckedOutChange);\n checkedOutHandle = null;\n descriptorSubscribers.clear();\n cloneResolutions.clear();\n };\n\n // Re-point the overlay at a new selection in place: reset the per-draft\n // state, then push fresh descriptors to every live subscriber.\n async function applyDraft(next: AutomergeUrl | null): Promise<void> {\n if (disposed) return;\n if (next === draftUrl) return;\n\n draftUrl = next;\n cloneResolutions.clear();\n\n // Reflect the selection for outside readers (e.g. the chat preview frame).\n // `draft-url` is not observed by <patchwork-view>, so this never remounts.\n element.setAttribute(\"draft-url\", next ?? \"\");\n\n draftReady = next\n ? (async () => {\n const handle = await liveRepo.find<DraftDoc>(next);\n if (disposed) {\n throw new Error(\"[drafts] provider disposed mid-load\");\n }\n return handle;\n })()\n : null;\n draftReady?.catch((err) => {\n console.error(`[drafts] failed to load draft overlay for ${next}:`, err);\n });\n\n refreshDescriptors();\n }\n\n // Re-answer every live descriptor subscription against the current\n // selection and checkpoint. Aborts the previous batch so slower, superseded\n // resolutions (including initial answers racing this refresh) stay silent.\n function refreshDescriptors(): void {\n refresh.abort();\n refresh = new AbortController();\n const { signal } = refresh;\n for (const subscriber of [...descriptorSubscribers]) {\n void resolveDescriptor(subscriber.original)\n .then((descriptor) => {\n if (signal.aborted) return;\n subscriber.respond(descriptor);\n })\n .catch((err) => {\n console.error(\n `[drafts] failed to re-map ${subscriber.original}:`,\n err\n );\n });\n }\n }\n\n // Resolve a `repo:handle-descriptor` request against the current selection.\n // The backing url is pinned to the active checkpoint's `to` heads for this\n // doc (if any) so nested views freeze with the doc they live in;\n // `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 the current draft and return its\n // url. Reuses an existing clone recorded in `DraftDoc.clones`; otherwise\n // forks `original` at its current heads and records the fork point so the\n // baseline 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 ready = draftReady;\n const promise = (async () => {\n if (!ready) {\n throw new Error(\"[drafts] resolveClone called without a draft\");\n }\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-D2vIz7hy.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 DocHandleDescriptor,\n type SubscribeEvent,\n} from \"@inkandswitch/patchwork-providers\";\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// Remaps documents resolved beneath it onto per-draft clones, so edits stay\n// inside the checked-out draft — and re-points *live* in place when the\n// selection changes, without the host remounting anything.\n//\n// The host `<patchwork-view>` wraps tool document resolution in an\n// `OverlayRepo` that opens a *streaming* `repo:handle-descriptor` subscription\n// per document. This provider always claims those subscriptions (even while\n// \"main\" is selected, where it answers a pass-through `{ url }`) and keeps the\n// `respond` callbacks registered. It follows the selection itself via the\n// ancestor draft-state provider's `draft:checked-out` doc: when\n// `CheckedOutDraft.checkedOut` changes, every live subscription is re-answered\n// with the new mapping — `{ url, cloneUrl }` on a draft (the clone is forked\n// eagerly on first resolution and recorded in `DraftDoc.clones`), `{ url }` on\n// main — and the `OverlayRepo` swaps handle backings in place.\n//\n// Descriptors also honor the active checkpoint: `CheckedOutDraft.at` maps each\n// member doc to per-doc `to`/`from` heads, and the `to` heads are baked onto\n// the backing url (the clone on a draft, the original on main) so nested views\n// freeze with the doc they live in; `OverlayRepo` honors heads on the backing\n// url. Checkpoint moves (history scrubbing rewrites `at` without changing\n// `checkedOut`) also re-answer every live subscription, so pins update by\n// swapping backings in place — no remount. The fork point lives in\n// `DraftDoc.clones[url].clonedAt`; the draft-state provider reads it to serve\n// `draft:baseline` (this provider no longer answers that).\n//\n// A `url` attribute, when present, seeds the initial selection. That is how\n// the chat preview iframe (see chat's `preview-frame.ts`) pins a\n// self-bootstrapped overlay to a specific draft in a realm that has no\n// draft-state provider to follow. The *current* selection is reflected onto the\n// (un-observed, so remount-free) `draft-url` attribute for outside readers.\nexport const DraftOverlayProvider = (element: HTMLElement) => {\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 // The checked-out draft this overlay currently maps onto. Null = \"main\"\n // (pass-through descriptors, save for checkpoint pinning).\n let draftUrl: AutomergeUrl | null = null;\n let draftReady: Promise<DocHandle<DraftDoc>> | null = null;\n // Aborted and replaced on every descriptor refresh (draft re-point or\n // checkpoint move), and aborted on dispose, so in-flight resolutions from a\n // superseded state can detect they lost the race and stay silent. The\n // signal only gates `respond` — it is never passed into the resolution\n // work, which is shared across batches (see `cloneResolutions`).\n let refresh = new AbortController();\n\n // One eager-clone resolution per original url; de-dupes concurrent requests.\n // Cleared on re-point (it is per-draft state).\n const cloneResolutions = new Map<AutomergeUrl, Promise<AutomergeUrl>>();\n\n // Live `repo:handle-descriptor` subscriptions, kept so a re-point can push\n // fresh descriptors to every consumer.\n type DescriptorSubscriber = {\n original: AutomergeUrl;\n respond: (descriptor: DocHandleDescriptor) => void;\n };\n const descriptorSubscribers = new Set<DescriptorSubscriber>();\n\n // Seed the selection from the `url` attribute when present (the chat\n // preview iframe mounts us with a pinned draft and no draft-state provider).\n const rawSeed = element.getAttribute(\"url\");\n if (rawSeed) {\n if (isValidAutomergeUrl(rawSeed)) {\n void applyDraft(rawSeed);\n } else {\n console.warn(\n `[drafts] <patchwork-view component=\"patchwork-draft-overlay-provider\"> ` +\n `has an invalid url attribute (got ${JSON.stringify(rawSeed)})`\n );\n }\n }\n\n // Follow the selection: the ancestor draft-state provider serves the\n // ephemeral CheckedOutDraft doc url; we watch its `checkedOut` live. The\n // same doc carries the checkpoint (`at`), read at resolve time so\n // descriptors can pin a nested doc to its per-doc `to` heads — absent means\n // render live.\n //\n // A draft switch re-points via `applyDraft` (resets per-draft state); a\n // checkpoint-only move (scrubbing rewrites `at` while `checkedOut` stays\n // put) just re-answers live subscriptions with freshly pinned descriptors.\n let checkedOutHandle: DocHandle<CheckedOutDraft> | null = null;\n let checkpointSignature = JSON.stringify(null);\n const onCheckedOutChange = () => {\n const doc = checkedOutHandle?.doc();\n const nextDraft = doc?.checkedOut ?? null;\n const nextSignature = JSON.stringify(doc?.at ?? null);\n const checkpointMoved = nextSignature !== checkpointSignature;\n checkpointSignature = nextSignature;\n if (nextDraft !== draftUrl) {\n void applyDraft(nextDraft);\n } else if (checkpointMoved) {\n refreshDescriptors();\n }\n };\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) return;\n checkedOutHandle?.off(\"change\", onCheckedOutChange);\n checkedOutHandle = handle;\n handle.on(\"change\", onCheckedOutChange);\n onCheckedOutChange();\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 const subscriber: DescriptorSubscriber = { original, respond };\n descriptorSubscribers.add(subscriber);\n const { signal } = refresh;\n void resolveDescriptor(original)\n .then((descriptor) => {\n // A re-point raced this resolution; `applyDraft`'s refresh pass\n // answers this subscriber with the new mapping instead.\n if (signal.aborted) return;\n respond(descriptor);\n })\n .catch((err) => {\n console.error(`[drafts] failed to resolve ${original}:`, err);\n });\n return () => {\n descriptorSubscribers.delete(subscriber);\n };\n });\n return;\n }\n };\n\n element.addEventListener(\"patchwork:subscribe\", onSubscribe);\n return () => {\n disposed = true;\n refresh.abort();\n element.removeEventListener(\"patchwork:subscribe\", onSubscribe);\n unsubscribeCheckedOut();\n checkedOutHandle?.off(\"change\", onCheckedOutChange);\n checkedOutHandle = null;\n descriptorSubscribers.clear();\n cloneResolutions.clear();\n };\n\n // Re-point the overlay at a new selection in place: reset the per-draft\n // state, then push fresh descriptors to every live subscriber.\n async function applyDraft(next: AutomergeUrl | null): Promise<void> {\n if (disposed) return;\n if (next === draftUrl) return;\n\n draftUrl = next;\n cloneResolutions.clear();\n\n // Reflect the selection for outside readers (e.g. the chat preview frame).\n // `draft-url` is not observed by <patchwork-view>, so this never remounts.\n element.setAttribute(\"draft-url\", next ?? \"\");\n\n draftReady = next\n ? (async () => {\n const handle = await liveRepo.find<DraftDoc>(next);\n if (disposed) {\n throw new Error(\"[drafts] provider disposed mid-load\");\n }\n return handle;\n })()\n : null;\n draftReady?.catch((err) => {\n console.error(`[drafts] failed to load draft overlay for ${next}:`, err);\n });\n\n refreshDescriptors();\n }\n\n // Re-answer every live descriptor subscription against the current\n // selection and checkpoint. Aborts the previous batch so slower, superseded\n // resolutions (including initial answers racing this refresh) stay silent.\n function refreshDescriptors(): void {\n refresh.abort();\n refresh = new AbortController();\n const { signal } = refresh;\n for (const subscriber of [...descriptorSubscribers]) {\n void resolveDescriptor(subscriber.original)\n .then((descriptor) => {\n if (signal.aborted) return;\n subscriber.respond(descriptor);\n })\n .catch((err) => {\n console.error(\n `[drafts] failed to re-map ${subscriber.original}:`,\n err\n );\n });\n }\n }\n\n // Resolve a `repo:handle-descriptor` request against the current selection.\n // The backing url is pinned to the active checkpoint's `to` heads for this\n // doc (if any) so nested views freeze with the doc they live in;\n // `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 the current draft and return its\n // url. Reuses an existing clone recorded in `DraftDoc.clones`; otherwise\n // forks `original` at its current heads and records the fork point so the\n // baseline 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 ready = draftReady;\n const promise = (async () => {\n if (!ready) {\n throw new Error(\"[drafts] resolveClone called without a draft\");\n }\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,14 +1,15 @@
1
1
  import { isValidAutomergeUrl, encodeHeads } from "@automerge/automerge-repo/slim";
2
- import { s as subscribe, a as accept } from "./index-FKZtof6Z.js";
3
- import { S as SKIPPED_DATATYPES, c as canonicalUrl } from "./clone-policy-CIdEhg7W.js";
4
- import { c as createChangeGrouper, e as ensureMainDraft } from "./draft-docs-Bi8K1y3a.js";
2
+ import { subscribe, accept } from "@inkandswitch/patchwork-providers";
3
+ import { c as canonicalUrl, S as SKIPPED_DATATYPES } from "./clone-policy-CIdEhg7W.js";
4
+ import { e as editCountsSince, c as createChangeGrouper, a as ensureMainDraft } from "./draft-docs-DyJ2l14d.js";
5
5
  import * as Automerge from "@automerge/automerge/slim";
6
- import "@automerge/automerge-repo";
7
- function createActorRecorder(element) {
6
+ function createActorRecorder(element, repo, resolveAttribution) {
8
7
  let contactUrl = null;
9
8
  let attributionHandle = null;
9
+ let attribution = null;
10
10
  const recordedActorIds = /* @__PURE__ */ new Set();
11
11
  const pendingActorIds = /* @__PURE__ */ new Set();
12
+ const watched = /* @__PURE__ */ new Map();
12
13
  let disposed = false;
13
14
  const unsubscribe = subscribe(
14
15
  element,
@@ -21,32 +22,79 @@ function createActorRecorder(element) {
21
22
  }
22
23
  }
23
24
  );
25
+ const onChange = (payload) => {
26
+ if (disposed) return;
27
+ const { before, after } = payload.patchInfo;
28
+ const actorId = Automerge.getActorId(after);
29
+ if (recordedActorIds.has(actorId)) return;
30
+ const since = Automerge.getHeads(before);
31
+ const ours = Automerge.getChangesMetaSince(after, since).filter(
32
+ (meta) => meta.actor === actorId
33
+ );
34
+ if (ours.length === 0) return;
35
+ void editCountsSince(after, since).then(
36
+ (counts) => {
37
+ if (disposed || !counts) return;
38
+ const edited = ours.some((meta) => {
39
+ const c = counts.get(meta.hash);
40
+ return !!c && c.additions + c.deletions > 0;
41
+ });
42
+ if (!edited) return;
43
+ pendingActorIds.add(actorId);
44
+ flushPendingActors();
45
+ },
46
+ () => {
47
+ }
48
+ );
49
+ };
24
50
  return {
25
- recordLocalChange(doc) {
51
+ watch(urls) {
26
52
  if (disposed) return;
27
- let actorId;
28
- try {
29
- actorId = Automerge.getActorId(doc);
30
- } catch {
31
- return;
53
+ const wanted = new Set(urls);
54
+ for (const [url, handle] of [...watched]) {
55
+ if (wanted.has(url)) continue;
56
+ handle?.off("change", onChange);
57
+ watched.delete(url);
58
+ }
59
+ for (const url of wanted) {
60
+ if (watched.has(url)) continue;
61
+ watched.set(url, null);
62
+ repo.find(url).then(
63
+ (handle) => {
64
+ if (disposed || watched.get(url) !== null) return;
65
+ watched.set(url, handle);
66
+ handle.on("change", onChange);
67
+ },
68
+ () => {
69
+ if (watched.get(url) === null) watched.delete(url);
70
+ }
71
+ );
32
72
  }
33
- if (recordedActorIds.has(actorId)) return;
34
- pendingActorIds.add(actorId);
35
- flushPendingActors();
36
- },
37
- setAttributionHandle(handle) {
38
- if (disposed) return;
39
- attributionHandle = handle;
40
- flushPendingActors();
41
73
  },
42
74
  dispose() {
43
75
  disposed = true;
44
76
  pendingActorIds.clear();
77
+ for (const [, handle] of watched) handle?.off("change", onChange);
78
+ watched.clear();
45
79
  unsubscribe();
46
80
  }
47
81
  };
48
82
  function flushPendingActors() {
49
- if (!attributionHandle || !contactUrl || pendingActorIds.size === 0) return;
83
+ if (disposed || !contactUrl || pendingActorIds.size === 0) return;
84
+ if (!attributionHandle) {
85
+ attribution ??= resolveAttribution().then(
86
+ (handle) => {
87
+ if (disposed) return;
88
+ attributionHandle = handle;
89
+ if (handle) flushPendingActors();
90
+ },
91
+ (err) => {
92
+ attribution = null;
93
+ console.warn("[drafts] failed to resolve actor attribution:", err);
94
+ }
95
+ );
96
+ return;
97
+ }
50
98
  const url = contactUrl;
51
99
  const actorIds = [...pendingActorIds];
52
100
  pendingActorIds.clear();
@@ -176,6 +224,23 @@ const DRAFT_LIST_SELECTOR = "draft:list";
176
224
  const BASELINE_SELECTOR = "draft:baseline";
177
225
  const ATTR_DOC_URL = "doc-url";
178
226
  const EMPTY_HEADS = encodeHeads([]);
227
+ const checkoutDocs = /* @__PURE__ */ new Map();
228
+ function checkoutFor(repo, docUrl) {
229
+ let handle = checkoutDocs.get(docUrl);
230
+ if (!handle) {
231
+ handle = repo.create({ checkedOut: null });
232
+ checkoutDocs.set(docUrl, handle);
233
+ return handle;
234
+ }
235
+ const doc = handle.doc();
236
+ if (doc?.checkedOut != null || doc?.at != null) {
237
+ handle.change((d) => {
238
+ d.checkedOut = null;
239
+ d.at = null;
240
+ });
241
+ }
242
+ return handle;
243
+ }
179
244
  const DraftStateProvider = (element) => {
180
245
  const rawUrl = element.getAttribute(ATTR_DOC_URL);
181
246
  if (!rawUrl || !isValidAutomergeUrl(rawUrl)) {
@@ -196,12 +261,12 @@ const DraftStateProvider = (element) => {
196
261
  }
197
262
  const repo = maybeRepo;
198
263
  let hostDocHandle = null;
199
- let checkedOutHandle = null;
200
- let draftRouter = null;
201
264
  const trackedDrafts = /* @__PURE__ */ new Map();
202
265
  let mainDraftHandle = null;
203
- const pendingCheckedOutSubscribers = /* @__PURE__ */ new Set();
266
+ const checkedOutHandle = checkoutFor(repo, docUrl);
267
+ const draftRouter = createDraftRouter(checkedOutHandle);
204
268
  const baselineSubscribers = /* @__PURE__ */ new Map();
269
+ const sentBaselines = /* @__PURE__ */ new Map();
205
270
  const listSubscribers = /* @__PURE__ */ new Set();
206
271
  let orderedDraftUrls = [];
207
272
  let draftList = {
@@ -216,29 +281,36 @@ const DraftStateProvider = (element) => {
216
281
  drafts: [],
217
282
  actorAttributionUrl: null
218
283
  };
219
- const actorRecorder = createActorRecorder(element);
220
- const changeGrouper = createChangeGrouper(repo, {
221
- onLocalChange: actorRecorder.recordLocalChange
222
- });
284
+ const actorRecorder = createActorRecorder(element, repo, ensureAttribution);
285
+ const changeGrouper = createChangeGrouper(repo);
223
286
  const mountCounts = /* @__PURE__ */ new Map();
224
287
  const skipVerdicts = /* @__PURE__ */ new Map();
225
288
  let disposed = false;
226
289
  let rewalkInFlight = false;
227
290
  let rewalkPending = false;
291
+ let cloneSyncTimer = null;
292
+ let mainDraftReady = null;
293
+ let attributionReady = null;
228
294
  const onTrackedChange = () => {
229
295
  scheduleRewalk();
230
- recomputeList();
296
+ reconcile();
297
+ };
298
+ const onHostDocChange = () => {
299
+ const url = hostDocHandle?.doc()?.["@patchwork"]?.mainDraftUrl ?? null;
300
+ if (url !== (mainDraftHandle?.url ?? null)) scheduleRewalk();
301
+ };
302
+ const onCheckedOutChange = () => {
303
+ notifyBaselines();
304
+ reconcile();
231
305
  };
232
- const onHostDocChange = () => scheduleRewalk();
233
- const onCheckedOutChange = () => notifyBaselines();
234
306
  const onMounted = (event) => {
235
307
  const detail = event.detail;
236
308
  if (!("url" in detail)) return;
237
309
  const url = canonicalUrl(detail.url);
238
310
  mountCounts.set(url, (mountCounts.get(url) ?? 0) + 1);
239
311
  ensureSkipVerdict(url);
240
- syncMainDraftClones();
241
- recomputeList();
312
+ scheduleCloneSync();
313
+ reconcile();
242
314
  };
243
315
  const onUnmounted = (event) => {
244
316
  const detail = event.detail;
@@ -247,40 +319,20 @@ const DraftStateProvider = (element) => {
247
319
  const count = mountCounts.get(url) ?? 0;
248
320
  if (count <= 1) mountCounts.delete(url);
249
321
  else mountCounts.set(url, count - 1);
250
- recomputeList();
322
+ reconcile();
251
323
  };
252
324
  const ready = (async () => {
253
325
  const handle = await repo.find(docUrl);
254
326
  if (disposed) return;
255
327
  hostDocHandle = handle;
256
- const hostType = handle.doc()?.["@patchwork"]?.type;
257
- if (hostType == null || !SKIPPED_DATATYPES.has(hostType)) {
258
- try {
259
- const mainDraft = await ensureMainDraft(repo, handle);
260
- if (disposed) return;
261
- const attribution = await ensureActorAttribution(repo, mainDraft);
262
- if (disposed) return;
263
- actorRecorder.setAttributionHandle(attribution);
264
- } catch (err) {
265
- console.warn("[drafts] failed to eagerly create main draft:", err);
266
- }
267
- }
268
- checkedOutHandle = repo.create({ checkedOut: null });
269
- checkedOutHandle.on("change", onCheckedOutChange);
270
- draftRouter = createDraftRouter(checkedOutHandle);
271
- const checkedOutUrl = checkedOutHandle.url;
272
- for (const respond of pendingCheckedOutSubscribers) {
273
- respond(checkedOutUrl);
274
- }
275
- pendingCheckedOutSubscribers.clear();
276
- notifyBaselines();
277
328
  handle.on("change", onHostDocChange);
278
329
  scheduleRewalk();
279
- recomputeList();
330
+ reconcile();
280
331
  })();
281
332
  ready.catch((err) => {
282
333
  console.error(`[drafts] failed to initialize draft-state provider:`, err);
283
334
  });
335
+ checkedOutHandle.on("change", onCheckedOutChange);
284
336
  const onSubscribe = (event) => {
285
337
  const { type } = event.detail.selector;
286
338
  if (type === ROOT_DOC_SELECTOR) {
@@ -291,12 +343,7 @@ const DraftStateProvider = (element) => {
291
343
  }
292
344
  if (type === CHECKED_OUT_SELECTOR) {
293
345
  accept(event, (respond) => {
294
- if (checkedOutHandle) {
295
- respond(checkedOutHandle.url);
296
- return;
297
- }
298
- pendingCheckedOutSubscribers.add(respond);
299
- return () => pendingCheckedOutSubscribers.delete(respond);
346
+ respond(checkedOutHandle.url);
300
347
  });
301
348
  return;
302
349
  }
@@ -304,7 +351,11 @@ const DraftStateProvider = (element) => {
304
351
  accept(event, (respond) => {
305
352
  respond(draftList);
306
353
  listSubscribers.add(respond);
307
- return () => listSubscribers.delete(respond);
354
+ if (listSubscribers.size === 1) activate();
355
+ return () => {
356
+ listSubscribers.delete(respond);
357
+ if (listSubscribers.size === 0) deactivate();
358
+ };
308
359
  });
309
360
  return;
310
361
  }
@@ -315,13 +366,18 @@ const DraftStateProvider = (element) => {
315
366
  }
316
367
  const target = canonicalUrl(rawTarget);
317
368
  accept(event, (respond) => {
318
- respond(currentBaseline(target));
369
+ const baseline = currentBaseline(target);
370
+ sentBaselines.set(target, baseline.heads);
371
+ respond(baseline);
319
372
  let set = baselineSubscribers.get(target);
320
373
  if (!set) baselineSubscribers.set(target, set = /* @__PURE__ */ new Set());
321
374
  set.add(respond);
322
375
  return () => {
323
376
  set.delete(respond);
324
- if (set.size === 0) baselineSubscribers.delete(target);
377
+ if (set.size === 0) {
378
+ baselineSubscribers.delete(target);
379
+ sentBaselines.delete(target);
380
+ }
325
381
  };
326
382
  });
327
383
  return;
@@ -332,33 +388,71 @@ const DraftStateProvider = (element) => {
332
388
  element.addEventListener("patchwork:unmounted", onUnmounted);
333
389
  return () => {
334
390
  disposed = true;
391
+ if (cloneSyncTimer !== null) clearTimeout(cloneSyncTimer);
335
392
  changeGrouper.dispose();
336
393
  actorRecorder.dispose();
337
- draftRouter?.dispose();
338
- draftRouter = null;
394
+ draftRouter.dispose();
339
395
  element.removeEventListener("patchwork:subscribe", onSubscribe);
340
396
  element.removeEventListener("patchwork:mounted", onMounted);
341
397
  element.removeEventListener("patchwork:unmounted", onUnmounted);
342
- if (hostDocHandle) hostDocHandle.off("change", onHostDocChange);
343
- if (mainDraftHandle) mainDraftHandle.off("change", onTrackedChange);
398
+ hostDocHandle?.off("change", onHostDocChange);
399
+ mainDraftHandle?.off("change", onTrackedChange);
344
400
  for (const [, h] of trackedDrafts) h.off("change", onTrackedChange);
401
+ checkedOutHandle.off("change", onCheckedOutChange);
345
402
  mainDraftHandle = null;
346
403
  trackedDrafts.clear();
347
- pendingCheckedOutSubscribers.clear();
348
404
  listSubscribers.clear();
349
405
  baselineSubscribers.clear();
406
+ sentBaselines.clear();
350
407
  mountCounts.clear();
351
408
  skipVerdicts.clear();
352
- if (checkedOutHandle) {
353
- checkedOutHandle.off("change", onCheckedOutChange);
354
- repo.delete(checkedOutHandle.url);
355
- }
356
- checkedOutHandle = null;
357
409
  hostDocHandle = null;
358
410
  };
411
+ function activate() {
412
+ ensureMainDraftDoc().then(
413
+ () => {
414
+ scheduleCloneSync();
415
+ reconcile();
416
+ },
417
+ (err) => console.warn("[drafts] failed to create main draft:", err)
418
+ );
419
+ reconcile();
420
+ }
421
+ function deactivate() {
422
+ changeGrouper.setTimelines([]);
423
+ }
424
+ function ensureMainDraftDoc() {
425
+ if (mainDraftReady) return mainDraftReady;
426
+ const attempt = (async () => {
427
+ await ready;
428
+ if (disposed || !hostDocHandle) return null;
429
+ const hostType = hostDocHandle.doc()?.["@patchwork"]?.type;
430
+ if (hostType != null && SKIPPED_DATATYPES.has(hostType)) return null;
431
+ const mainDraft = await ensureMainDraft(repo, hostDocHandle);
432
+ if (disposed) return null;
433
+ scheduleRewalk();
434
+ return mainDraft;
435
+ })();
436
+ mainDraftReady = attempt;
437
+ attempt.catch(() => {
438
+ if (mainDraftReady === attempt) mainDraftReady = null;
439
+ });
440
+ return attempt;
441
+ }
442
+ function ensureAttribution() {
443
+ if (attributionReady) return attributionReady;
444
+ const attempt = ensureMainDraftDoc().then(
445
+ (mainDraft) => mainDraft && !disposed ? ensureActorAttribution(repo, mainDraft) : null
446
+ );
447
+ attributionReady = attempt;
448
+ attempt.catch(() => {
449
+ if (attributionReady === attempt) attributionReady = null;
450
+ });
451
+ return attempt;
452
+ }
359
453
  function scheduleRewalk() {
360
454
  if (disposed) return;
361
- if (!hostDocHandle || !checkedOutHandle) return;
455
+ if (!hostDocHandle) return;
362
456
  if (rewalkInFlight) {
363
457
  rewalkPending = true;
364
458
  return;
@@ -375,16 +469,23 @@ const DraftStateProvider = (element) => {
375
469
  repo,
376
470
  roots,
377
471
  trackedDrafts,
378
- onTrackedChange
472
+ onTrackedChange,
473
+ () => disposed
379
474
  );
380
475
  if (disposed) return;
476
+ const reachable = new Set(allDrafts);
477
+ for (const [url, handle] of trackedDrafts) {
478
+ if (reachable.has(url)) continue;
479
+ handle.off("change", onTrackedChange);
480
+ trackedDrafts.delete(url);
481
+ }
381
482
  orderedDraftUrls = allDrafts;
382
- draftRouter?.updateAvailableDrafts(allDrafts);
483
+ draftRouter.updateAvailableDrafts(allDrafts);
383
484
  } catch (err) {
384
485
  console.error("[drafts] rewalk failed:", err);
385
486
  } finally {
386
487
  rewalkInFlight = false;
387
- recomputeList();
488
+ reconcile();
388
489
  if (rewalkPending) {
389
490
  rewalkPending = false;
390
491
  scheduleRewalk();
@@ -392,14 +493,26 @@ const DraftStateProvider = (element) => {
392
493
  }
393
494
  })();
394
495
  }
395
- function recomputeList() {
496
+ function reconcile() {
396
497
  if (disposed) return;
397
- updateChangeGrouping();
498
+ actorRecorder.watch(writableDocUrls());
499
+ if (listSubscribers.size > 0) updateChangeGrouping();
398
500
  const next = computeList();
399
501
  if (draftListsEqual(draftList, next)) return;
400
502
  draftList = next;
401
503
  for (const respond of listSubscribers) respond(next);
402
504
  }
505
+ function writableDocUrls() {
506
+ const urls = [...mountCounts.keys()].filter(
507
+ (url) => skipVerdicts.get(url) === false
508
+ );
509
+ const selected = checkedOutHandle.doc()?.checkedOut ?? null;
510
+ const clones = selected ? trackedDrafts.get(selected)?.doc()?.clones : void 0;
511
+ for (const entry of Object.values(clones ?? {})) {
512
+ urls.push(canonicalUrl(entry.cloneUrl));
513
+ }
514
+ return urls;
515
+ }
403
516
  function updateChangeGrouping() {
404
517
  if (disposed) return;
405
518
  const specs = [];
@@ -410,7 +523,7 @@ const DraftStateProvider = (element) => {
410
523
  rootDocUrl: docUrl
411
524
  });
412
525
  }
413
- const selected = checkedOutHandle?.doc()?.checkedOut ?? null;
526
+ const selected = checkedOutHandle.doc()?.checkedOut ?? null;
414
527
  const ordered = selected && orderedDraftUrls.includes(selected) ? [selected, ...orderedDraftUrls.filter((u) => u !== selected)] : orderedDraftUrls;
415
528
  for (const url of ordered) {
416
529
  const handle = trackedDrafts.get(url);
@@ -475,12 +588,16 @@ const DraftStateProvider = (element) => {
475
588
  return { url, parent: null, members, childCount, name, changeGroupDocUrl };
476
589
  }
477
590
  function currentBaseline(target) {
478
- const entry = checkedOutHandle?.doc()?.at?.[target];
591
+ const entry = checkedOutHandle.doc()?.at?.[target];
479
592
  return { heads: entry?.from ?? null };
480
593
  }
481
594
  function notifyBaselines() {
482
595
  for (const [target, set] of baselineSubscribers) {
483
596
  const baseline = currentBaseline(target);
597
+ if (sameHeads(baseline.heads, sentBaselines.get(target) ?? null)) {
598
+ continue;
599
+ }
600
+ sentBaselines.set(target, baseline.heads);
484
601
  for (const respond of [...set]) respond(baseline);
485
602
  }
486
603
  }
@@ -494,8 +611,8 @@ const DraftStateProvider = (element) => {
494
611
  const skipped = type != null && SKIPPED_DATATYPES.has(type);
495
612
  if (skipVerdicts.get(url) === skipped) return;
496
613
  skipVerdicts.set(url, skipped);
497
- syncMainDraftClones();
498
- recomputeList();
614
+ scheduleCloneSync();
615
+ reconcile();
499
616
  } catch {
500
617
  }
501
618
  })();
@@ -515,8 +632,15 @@ const DraftStateProvider = (element) => {
515
632
  syncMainDraftClones();
516
633
  return handle;
517
634
  }
635
+ function scheduleCloneSync() {
636
+ if (cloneSyncTimer !== null) return;
637
+ cloneSyncTimer = setTimeout(() => {
638
+ cloneSyncTimer = null;
639
+ syncMainDraftClones();
640
+ }, 0);
641
+ }
518
642
  function syncMainDraftClones() {
519
- if (disposed || !mainDraftHandle) return;
643
+ if (disposed || !mainDraftHandle || listSubscribers.size === 0) return;
520
644
  const existing = mainDraftHandle.doc()?.clones ?? {};
521
645
  const toAdd = [...mountCounts.keys()].filter(
522
646
  (url) => skipVerdicts.get(url) === false && !existing[url]
@@ -531,7 +655,7 @@ const DraftStateProvider = (element) => {
531
655
  });
532
656
  }
533
657
  };
534
- async function collectAllDrafts(repo, roots, tracked, onNewChange) {
658
+ async function collectAllDrafts(repo, roots, tracked, onNewChange, isDisposed) {
535
659
  const visited = /* @__PURE__ */ new Set();
536
660
  const order = [];
537
661
  const queue = [...roots];
@@ -543,6 +667,7 @@ async function collectAllDrafts(repo, roots, tracked, onNewChange) {
543
667
  let h = tracked.get(url);
544
668
  if (!h) {
545
669
  h = await repo.find(url);
670
+ if (isDisposed()) return order;
546
671
  tracked.set(url, h);
547
672
  h.on("change", onNewChange);
548
673
  }
@@ -591,4 +716,4 @@ function sameHeads(a, b) {
591
716
  export {
592
717
  DraftStateProvider
593
718
  };
594
- //# sourceMappingURL=DraftStateProvider-DX8b_RgQ.js.map
719
+ //# sourceMappingURL=DraftStateProvider-Bhz0S4c1.js.map