@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.
- package/module-settings-url +1 -0
- package/package.json +2 -1
- package/packages/codemirror-markdown/example.js +1 -2
- package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js → DraftOverlayProvider-D2vIz7hy.js} +2 -3
- package/packages/drafts/dist/assets/{DraftOverlayProvider-VYKlzOy1.js.map → DraftOverlayProvider-D2vIz7hy.js.map} +1 -1
- package/packages/drafts/dist/assets/{DraftStateProvider-DX8b_RgQ.js → DraftStateProvider-Bhz0S4c1.js} +212 -87
- package/packages/drafts/dist/assets/DraftStateProvider-Bhz0S4c1.js.map +1 -0
- package/packages/drafts/dist/assets/{draft-docs-Bi8K1y3a.js → draft-docs-DyJ2l14d.js} +108 -99
- package/packages/drafts/dist/assets/draft-docs-DyJ2l14d.js.map +1 -0
- package/packages/drafts/dist/assets/{main-EEciTwbZ.js → main-CG9HOCai.js} +14 -10
- package/packages/drafts/dist/assets/main-CG9HOCai.js.map +1 -0
- package/packages/drafts/dist/index.js +9 -9
- package/packages/drafts/package.json +5 -5
- package/packages/sideboard/dist/{chunk-KHS2XJKP.js → chunk-6POQCUBB.js} +3 -3
- package/packages/sideboard/dist/{chunk-TXFTRSLY.js → chunk-DS3QVJZT.js} +3 -3
- package/packages/sideboard/dist/chunk-DS3QVJZT.js.map +7 -0
- package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js → create-new-menu-VA4ELRJ6.js} +2 -2
- package/packages/sideboard/dist/document-list-panel-GHYN6JR5.js +2 -0
- package/packages/sideboard/dist/index.js +1 -1
- package/packages/sideboard/dist/sideboard/sideboard.js +1 -1
- package/packages/tldraw4/dist/{chunk-IKZWERSR.js → chunk-G3PMV62Z.js} +1 -3
- package/packages/tldraw4/dist/{chunk-7CJQMVDI.js → chunk-PMHAL5YM.js} +4 -4
- package/packages/tldraw4/dist/datatype.js +2 -2
- package/packages/tldraw4/dist/main.js +1 -1
- package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js → sanitizeSvg-NCGPTJ3E.js} +2 -2
- package/packages/tldraw4/dist/tool.js +450 -5675
- package/packages/tldraw4/dist/tool.js.map +4 -4
- package/packages/tldraw4/package.json +0 -1
- package/packages/tldraw5/dist/{chunk-GKLJTYCF.js → chunk-DO47QRII.js} +1 -3
- package/packages/tldraw5/dist/{chunk-4JN3PIR7.js → chunk-FTKY6DDS.js} +3 -3
- package/packages/tldraw5/dist/{chunk-JDHJ334K.js → chunk-SZI4VCOI.js} +4 -4
- package/packages/tldraw5/dist/datatype.js +3 -3
- package/packages/tldraw5/dist/main.js +1 -1
- package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js → sanitizeSvg-J7F3MQGT.js} +2 -2
- package/packages/tldraw5/dist/{script-VW3AAU5T.js → script-4RKE5JKT.js} +3 -3
- package/packages/tldraw5/dist/script-4RKE5JKT.js.map +7 -0
- package/packages/tldraw5/dist/{tldr-archive-WERHN3T4.js → tldr-archive-5X7EMHWZ.js} +3 -3
- package/packages/tldraw5/dist/tool.js +293 -1543
- package/packages/tldraw5/dist/tool.js.map +4 -4
- package/packages/tldraw5/package.json +0 -1
- package/plugin-registry-url +1 -0
- package/packages/drafts/dist/assets/DraftStateProvider-DX8b_RgQ.js.map +0 -1
- package/packages/drafts/dist/assets/draft-docs-Bi8K1y3a.js.map +0 -1
- package/packages/drafts/dist/assets/index-FKZtof6Z.js +0 -67
- package/packages/drafts/dist/assets/index-FKZtof6Z.js.map +0 -1
- package/packages/drafts/dist/assets/main-EEciTwbZ.js.map +0 -1
- package/packages/sideboard/dist/chunk-TXFTRSLY.js.map +0 -7
- package/packages/sideboard/dist/document-list-panel-WDIVYTZU.js +0 -2
- package/packages/tldraw5/dist/script-VW3AAU5T.js.map +0 -7
- /package/packages/sideboard/dist/{chunk-KHS2XJKP.js.map → chunk-6POQCUBB.js.map} +0 -0
- /package/packages/sideboard/dist/{create-new-menu-EU5KJJ3R.js.map → create-new-menu-VA4ELRJ6.js.map} +0 -0
- /package/packages/sideboard/dist/{document-list-panel-WDIVYTZU.js.map → document-list-panel-GHYN6JR5.js.map} +0 -0
- /package/packages/tldraw4/dist/{chunk-IKZWERSR.js.map → chunk-G3PMV62Z.js.map} +0 -0
- /package/packages/tldraw4/dist/{chunk-7CJQMVDI.js.map → chunk-PMHAL5YM.js.map} +0 -0
- /package/packages/tldraw4/dist/{sanitizeSvg-OOYBXUQ2.js.map → sanitizeSvg-NCGPTJ3E.js.map} +0 -0
- /package/packages/tldraw5/dist/{chunk-GKLJTYCF.js.map → chunk-DO47QRII.js.map} +0 -0
- /package/packages/tldraw5/dist/{chunk-4JN3PIR7.js.map → chunk-FTKY6DDS.js.map} +0 -0
- /package/packages/tldraw5/dist/{chunk-JDHJ334K.js.map → chunk-SZI4VCOI.js.map} +0 -0
- /package/packages/tldraw5/dist/{sanitizeSvg-3EKLLIYO.js.map → sanitizeSvg-J7F3MQGT.js.map} +0 -0
- /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.
|
|
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 {
|
|
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-
|
|
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":";;;AAkBA,MAAM,6BAA6B;AACnC,MAAM,uBAAuB;AAgCtB,MAAM,uBAAuB,CAAC,YAAyB;AAC5D,QAAM,OAAO,UAAU,SAAS,OAAO,OAAO;AAC9C,MAAI,CAAC,MAAM;AACT,YAAQ;AAAA,MACN;AAAA,IAAA;AAEF,WAAO,MAAM;AAAA,IAAC;AAAA,EAChB;AACA,QAAM,WAAW;AAEjB,MAAI,WAAW;AAIf,MAAI,WAAgC;AACpC,MAAI,aAAkD;AAMtD,MAAI,UAAU,IAAI,gBAAA;AAIlB,QAAM,uCAAuB,IAAA;AAQ7B,QAAM,4CAA4B,IAAA;AAIlC,QAAM,UAAU,QAAQ,aAAa,KAAK;AAC1C,MAAI,SAAS;AACX,QAAI,oBAAoB,OAAO,GAAG;AAChC,WAAK,WAAW,OAAO;AAAA,IACzB,OAAO;AACL,cAAQ;AAAA,QACN,4GACuC,KAAK,UAAU,OAAO,CAAC;AAAA,MAAA;AAAA,IAElE;AAAA,EACF;AAWA,MAAI,mBAAsD;AAC1D,MAAI,sBAAsB,KAAK,UAAU,IAAI;AAC7C,QAAM,qBAAqB,MAAM;AAC/B,UAAM,MAAM,kBAAkB,IAAA;AAC9B,UAAM,YAAY,KAAK,cAAc;AACrC,UAAM,gBAAgB,KAAK,UAAU,KAAK,MAAM,IAAI;AACpD,UAAM,kBAAkB,kBAAkB;AAC1C,0BAAsB;AACtB,QAAI,cAAc,UAAU;AAC1B,WAAK,WAAW,SAAS;AAAA,IAC3B,WAAW,iBAAiB;AAC1B,yBAAA;AAAA,IACF;AAAA,EACF;AACA,QAAM,wBAAwB;AAAA,IAC5B;AAAA,IACA,EAAE,MAAM,qBAAA;AAAA,IACR,CAAC,QAAQ;AACP,UAAI,YAAY,CAAC,oBAAoB,GAAG,EAAG;AAC3C,WAAK,SAAS,KAAsB,GAAG,EAAE,KAAK,CAAC,WAAW;AACxD,YAAI,YAAY,qBAAqB,OAAQ;AAC7C,0BAAkB,IAAI,UAAU,kBAAkB;AAClD,2BAAmB;AACnB,eAAO,GAAG,UAAU,kBAAkB;AACtC,2BAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EAAA;AAGF,QAAM,cAAc,CAAC,UAA0B;AAC7C,UAAM,WAAW,MAAM,OAAO;AAE9B,QAAI,SAAS,SAAS,4BAA4B;AAChD,YAAM,YAAY,SAAS;AAC3B,UAAI,OAAO,cAAc,YAAY,CAAC,oBAAoB,SAAS,GAAG;AACpE;AAAA,MACF;AACA,YAAM,WAAW,aAAa,SAAS;AACvC,aAA4B,OAAO,CAAC,YAAY;AAC9C,cAAM,aAAmC,EAAE,UAAU,QAAA;AACrD,8BAAsB,IAAI,UAAU;AACpC,cAAM,EAAE,WAAW;AACnB,aAAK,kBAAkB,QAAQ,EAC5B,KAAK,CAAC,eAAe;AAGpB,cAAI,OAAO,QAAS;AACpB,kBAAQ,UAAU;AAAA,QACpB,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,kBAAQ,MAAM,8BAA8B,QAAQ,KAAK,GAAG;AAAA,QAC9D,CAAC;AACH,eAAO,MAAM;AACX,gCAAsB,OAAO,UAAU;AAAA,QACzC;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAAA,EACF;AAEA,UAAQ,iBAAiB,uBAAuB,WAAW;AAC3D,SAAO,MAAM;AACX,eAAW;AACX,YAAQ,MAAA;AACR,YAAQ,oBAAoB,uBAAuB,WAAW;AAC9D,0BAAA;AACA,sBAAkB,IAAI,UAAU,kBAAkB;AAClD,uBAAmB;AACnB,0BAAsB,MAAA;AACtB,qBAAiB,MAAA;AAAA,EACnB;AAIA,iBAAe,WAAW,MAA0C;AAClE,QAAI,SAAU;AACd,QAAI,SAAS,SAAU;AAEvB,eAAW;AACX,qBAAiB,MAAA;AAIjB,YAAQ,aAAa,aAAa,QAAQ,EAAE;AAE5C,iBAAa,QACR,YAAY;AACX,YAAM,SAAS,MAAM,SAAS,KAAe,IAAI;AACjD,UAAI,UAAU;AACZ,cAAM,IAAI,MAAM,qCAAqC;AAAA,MACvD;AACA,aAAO;AAAA,IACT,OACA;AACJ,gBAAY,MAAM,CAAC,QAAQ;AACzB,cAAQ,MAAM,6CAA6C,IAAI,KAAK,GAAG;AAAA,IACzE,CAAC;AAED,uBAAA;AAAA,EACF;AAKA,WAAS,qBAA2B;AAClC,YAAQ,MAAA;AACR,cAAU,IAAI,gBAAA;AACd,UAAM,EAAE,WAAW;AACnB,eAAW,cAAc,CAAC,GAAG,qBAAqB,GAAG;AACnD,WAAK,kBAAkB,WAAW,QAAQ,EACvC,KAAK,CAAC,eAAe;AACpB,YAAI,OAAO,QAAS;AACpB,mBAAW,QAAQ,UAAU;AAAA,MAC/B,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,gBAAQ;AAAA,UACN,6BAA6B,WAAW,QAAQ;AAAA,UAChD;AAAA,QAAA;AAAA,MAEJ,CAAC;AAAA,IACL;AAAA,EACF;AASA,iBAAe,kBACb,UAC8B;AAC9B,UAAM,KAAK,kBAAkB,IAAA,GAAO,KAAK,QAAQ,GAAG,MAAM;AAC1D,QAAI,CAAC,YAAa,MAAM,aAAa,QAAQ,GAAI;AAC/C,aAAO,KACH,EAAE,KAAK,UAAU,UAAU,UAAU,UAAU,EAAE,EAAA,IACjD,EAAE,KAAK,SAAA;AAAA,IACb;AACA,UAAM,WAAW,MAAM,aAAa,QAAQ;AAC5C,WAAO,EAAE,KAAK,UAAU,UAAU,UAAU,UAAU,EAAE,EAAA;AAAA,EAC1D;AAKA,WAAS,UACP,KACA,OACc;AACd,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,sBAAsB;AAAA,MAC3B,YAAY,kBAAkB,GAAG,EAAE;AAAA,MACnC;AAAA,IAAA,CACD;AAAA,EACH;AAMA,iBAAe,aAAa,UAA0C;AACpE,QAAI;AACF,YAAM,SAAS,MAAM,SAAS;AAAA,QAC5B;AAAA,MAAA;AAEF,YAAM,OAAO,OAAO,IAAA,IAAQ,YAAY,GAAG;AAC3C,aAAO,QAAQ,QAAQ,kBAAkB,IAAI,IAAI;AAAA,IACnD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAMA,WAAS,aAAa,UAA+C;AACnE,UAAM,SAAS,iBAAiB,IAAI,QAAQ;AAC5C,QAAI,OAAQ,QAAO;AACnB,UAAM,QAAQ;AACd,UAAM,WAAW,YAAY;AAC3B,UAAI,CAAC,OAAO;AACV,cAAM,IAAI,MAAM,8CAA8C;AAAA,MAChE;AACA,YAAM,SAAS,MAAM;AACrB,YAAM,WAAW,OAAO,IAAA,GAAO,SAAS,QAAQ;AAChD,UAAI,SAAU,QAAO,aAAa,SAAS,QAAQ;AAEnD,YAAM,iBAAiB,MAAM,SAAS,KAAc,QAAQ;AAC5D,YAAM,WAAW,eAAe,MAAA;AAChC,YAAM,QAAQ,SAAS,MAAM,cAAc;AAC3C,YAAM,WAAW,aAAa,MAAM,GAAG;AAEvC,aAAO,OAAO,CAAC,MAAM;AACnB,UAAE,OAAO,QAAQ,IAAI,EAAE,UAAU,SAAA;AAAA,MACnC,CAAC;AAED,aAAO;AAAA,IACT,GAAA;AACA,qBAAiB,IAAI,UAAU,OAAO;AACtC,WAAO;AAAA,EACT;AACF;"}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
import { isValidAutomergeUrl, encodeHeads } from "@automerge/automerge-repo/slim";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { c as createChangeGrouper,
|
|
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
|
-
|
|
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
|
-
|
|
51
|
+
watch(urls) {
|
|
26
52
|
if (disposed) return;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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 (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
|
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
|
-
|
|
343
|
-
|
|
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
|
|
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
|
|
483
|
+
draftRouter.updateAvailableDrafts(allDrafts);
|
|
383
484
|
} catch (err) {
|
|
384
485
|
console.error("[drafts] rewalk failed:", err);
|
|
385
486
|
} finally {
|
|
386
487
|
rewalkInFlight = false;
|
|
387
|
-
|
|
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
|
|
496
|
+
function reconcile() {
|
|
396
497
|
if (disposed) return;
|
|
397
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
498
|
-
|
|
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-
|
|
719
|
+
//# sourceMappingURL=DraftStateProvider-Bhz0S4c1.js.map
|