diff --git a/sideboard/src/sideboard/account-bar.tsx b/sideboard/src/sideboard/account-bar.tsx index acddcecc..6382461c 100644 --- a/sideboard/src/sideboard/account-bar.tsx +++ b/sideboard/src/sideboard/account-bar.tsx @@ -1,4 +1,4 @@ -import { makeDocumentProjection } from "solid-automerge"; +import { makeDocumentProjection } from "./lib/document-projection.ts"; import { Show } from "solid-js"; import { render } from "solid-js/web"; import type { DocHandle, Repo } from "@automerge/automerge-repo/slim"; diff --git a/sideboard/src/sideboard/document-list-panel.tsx b/sideboard/src/sideboard/document-list-panel.tsx index 0fcf57a1..b94dd6fd 100644 --- a/sideboard/src/sideboard/document-list-panel.tsx +++ b/sideboard/src/sideboard/document-list-panel.tsx @@ -1,4 +1,4 @@ -import { useDocument } from "solid-automerge"; +import { useDocument } from "./lib/document-projection.ts"; import type { AutomergeUrl, Repo } from "@automerge/automerge-repo/slim"; import type { PatchworkViewElement } from "@inkandswitch/patchwork-elements"; import type { OpenDocumentEventDetail } from "@inkandswitch/patchwork-elements"; diff --git a/sideboard/src/sideboard/document-list/folder.tsx b/sideboard/src/sideboard/document-list/folder.tsx index 1597ed99..527bd40f 100644 --- a/sideboard/src/sideboard/document-list/folder.tsx +++ b/sideboard/src/sideboard/document-list/folder.tsx @@ -4,7 +4,7 @@ import { type Repo, type DocHandle, } from "@automerge/automerge-repo/slim"; -import { useDocument } from "solid-automerge"; +import { useDocument } from "../lib/document-projection.ts"; import type { OpenDocumentEventDetail, PatchworkViewElement, diff --git a/sideboard/src/sideboard/lib/document-projection.test.ts b/sideboard/src/sideboard/lib/document-projection.test.ts new file mode 100644 index 00000000..28e272c4 --- /dev/null +++ b/sideboard/src/sideboard/lib/document-projection.test.ts @@ -0,0 +1,135 @@ +import { describe, expect, it } from "vitest"; +import { createRoot } from "solid-js"; +import { unwrap } from "solid-js/store"; +import type { + DocHandle, + DocHandleChangePayload, + Patch, +} from "@automerge/automerge-repo/slim"; +import { makeDocumentProjection } from "./document-projection.ts"; + +type FolderDoc = { + title: string; + docs: { url: string; name: string; type: string }[]; +}; + +// The patches automerge emits for `folder.docs.push({name, type, url})`. +const insertPatches: Patch[] = [ + { action: "insert", path: ["docs", 0], values: [{}] }, + { action: "put", path: ["docs", 0, "name"], value: "" }, + { action: "put", path: ["docs", 0, "type"], value: "" }, + { action: "put", path: ["docs", 0, "url"], value: "" }, + { action: "splice", path: ["docs", 0, "name", 0], value: "Untitled" }, + { action: "splice", path: ["docs", 0, "type", 0], value: "markdown" }, + { action: "splice", path: ["docs", 0, "url", 0], value: "automerge:abc" }, +]; + +describe("makeDocumentProjection", () => { + it("applies a change once per projection even when handles share a doc object", () => { + // Several handle objects for the same document all hand out the same + // materialized doc — as overlay handles from different s do. + const document = createFakeDocument({ title: "f", docs: [] }); + const handles = [document.handle(), document.handle(), document.handle()]; + + createRoot((dispose) => { + const stores = handles.map((h) => makeDocumentProjection(h)); + + document.change(insertPatches, { title: "f", docs: [{ name: "Untitled", type: "markdown", url: "automerge:abc" }] }); + + for (const store of stores) { + expect(store.docs.map((d) => d.url)).toEqual(["automerge:abc"]); + } + expect(document.doc().docs).toHaveLength(1); + dispose(); + }); + }); + + it("never writes into the automerge document's own object", () => { + const document = createFakeDocument({ title: "f", docs: [] }); + const raw = document.doc(); + + createRoot((dispose) => { + const store = makeDocumentProjection(document.handle()); + expect(unwrap(store)).not.toBe(raw); + + document.change(insertPatches, { title: "f", docs: [{ name: "Untitled", type: "markdown", url: "automerge:abc" }] }); + + expect(store.docs).toHaveLength(1); + expect(raw.docs).toHaveLength(0); + dispose(); + }); + }); + + it("shares one store between projections of the same handle and tears it down last", () => { + const document = createFakeDocument({ title: "f", docs: [] }); + const handle = document.handle(); + + const disposeA = createRoot((dispose) => { + makeDocumentProjection(handle); + return dispose; + }); + let storeB!: FolderDoc; + const disposeB = createRoot((dispose) => { + storeB = makeDocumentProjection(handle); + return dispose; + }); + + expect(document.listenerCount()).toBe(1); + disposeA(); + expect(document.listenerCount()).toBe(1); + + document.change(insertPatches, { title: "f", docs: [{ name: "Untitled", type: "markdown", url: "automerge:abc" }] }); + expect(storeB.docs).toHaveLength(1); + + disposeB(); + expect(document.listenerCount()).toBe(0); + }); +}); + +// A stand-in for an automerge document that several handles point at: every +// handle's `doc()` returns the same object, and a change fans out to every +// handle's listeners with the same patches — the shape of the real thing. +function createFakeDocument(initial: T) { + let current: T = initial; + const listeners = new Set<{ event: string; fn: (p: unknown) => void }>(); + + function handle(): DocHandle { + const self = { + url: "automerge:fake", + doc: () => current, + on(event: string, fn: (p: unknown) => void) { + listeners.add({ event, fn }); + return self; + }, + off(event: string, fn: (p: unknown) => void) { + for (const l of listeners) { + if (l.event === event && l.fn === fn) listeners.delete(l); + } + return self; + }, + }; + return self as unknown as DocHandle; + } + + function change(patches: Patch[], after: T) { + const before = current; + current = after; + for (const l of [...listeners]) { + if (l.event !== "change") continue; + l.fn({ + handle: undefined, + doc: after, + patches, + patchInfo: { before, after, source: "change" }, + } satisfies Partial>); + } + } + + return { + handle, + change, + doc: () => current, + listenerCount: () => + [...listeners].filter((l) => l.event === "change").length, + }; +} diff --git a/sideboard/src/sideboard/lib/document-projection.ts b/sideboard/src/sideboard/lib/document-projection.ts new file mode 100644 index 00000000..76270df9 --- /dev/null +++ b/sideboard/src/sideboard/lib/document-projection.ts @@ -0,0 +1,110 @@ +import type { + AutomergeUrl, + Doc, + DocHandle, + DocHandleChangePayload, +} from "@automerge/automerge-repo/slim"; +import { createMemo, onCleanup, type Accessor, type Resource } from "solid-js"; +import { createStore, produce, reconcile, type Store } from "solid-js/store"; +import { autoproduce, useDocHandle } from "solid-automerge"; + +/** + * Drop-in replacements for solid-automerge's `useDocument` and + * `makeDocumentProjection`. + * + * solid-automerge (as of 2.0.1) builds its store with + * `createStore(handle.doc())`, i.e. the automerge document's own materialized + * object becomes the store's raw object, and every patch is written into it + * in place. `handle.doc()` returns the same object for every handle of a + * document, so two projections on two handle objects of the same doc — which + * is exactly what happens when several ``s each hold their + * own overlay handle for the folder that's open — end up mutating one shared + * array. Each projection applies the same insert patch, and a document added + * to a folder shows up as many times as there are projections until a reload + * rebuilds the store from scratch. + * + * Here the store starts from (and is only ever reconciled against) a copy of + * the document, so a projection owns its state outright. + */ + +type UseDocHandleOptions = Parameters[1]; + +export function useDocument( + url: Accessor, + options?: UseDocHandleOptions +): [Accessor | undefined>, Resource | undefined>] { + const handle = useDocHandle(url, options); + const projection = createMemo | undefined>(() => { + const h = handle(); + return h && makeDocumentProjection(h); + }); + return [projection, handle]; +} + +// `scopeReplaced` arrived with sub-handles in newer automerge-repo builds than +// the one this package types against; the shell's runtime does send it. +type ChangePayload = DocHandleChangePayload & { scopeReplaced?: boolean }; + +type CacheEntry = { + refs: number; + store: Store>; + cleanup(): void; +}; + +// One store per handle object, shared by everything projecting that handle +// within this bundle and torn down when the last user goes away. +const cache = new WeakMap, CacheEntry>(); + +export function makeDocumentProjection( + handle: DocHandle +): Doc { + onCleanup(() => { + const entry = cache.get(handle); + if (entry && --entry.refs === 0) entry.cleanup(); + }); + + const existing = cache.get(handle); + if (existing) { + existing.refs++; + return existing.store as Doc; + } + + const [doc, set] = createStore>(snapshot(handle.doc()!)); + + function patch(payload: ChangePayload) { + // `scopeReplaced`: the change landed at or above this handle's scope, so + // there are no in-scope patches; reconcile against the new value instead. + // `doc` is undefined when the scope was removed entirely. + if (payload.scopeReplaced) { + set(reconcile(snapshot((payload.doc ?? {}) as Doc))); + return; + } + set(produce(autoproduce(payload))); + } + + function ondelete() { + set(reconcile({} as Doc)); + } + + handle.on("change", patch); + handle.on("delete", ondelete); + + cache.set(handle, { + refs: 1, + store: doc, + cleanup() { + handle.off("change", patch); + handle.off("delete", ondelete); + cache.delete(handle); + }, + }); + + return doc; +} + +// A copy the store can own. Automerge's materialized doc is plain data, so a +// structured clone is faithful; it also sheds automerge's symbol-keyed internal +// state, which has no business in a Solid store. +function snapshot(doc: T): T { + return structuredClone(doc); +} diff --git a/sideboard/src/sideboard/sideboard.tsx b/sideboard/src/sideboard/sideboard.tsx index b6d6487d..95b90d44 100644 --- a/sideboard/src/sideboard/sideboard.tsx +++ b/sideboard/src/sideboard/sideboard.tsx @@ -1,4 +1,4 @@ -import { makeDocumentProjection } from "solid-automerge"; +import { makeDocumentProjection } from "./lib/document-projection.ts"; import { Show } from "solid-js"; import { render } from "solid-js/web"; import type { DocHandle, Repo } from "@automerge/automerge-repo/slim"; diff --git a/tldraw4/package.json b/tldraw4/package.json index 577f27cc..8252aeef 100644 --- a/tldraw4/package.json +++ b/tldraw4/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@automerge/automerge-repo-react-hooks": "^2.6.0-subduction.46", - "@automerge/react": "^2.6.0-subduction.9", "@inkandswitch/patchwork-bootloader": "^0.7.2", "@inkandswitch/patchwork-filesystem": "^0.2.9", "@inkandswitch/patchwork-plugins": "^1.2.5", diff --git a/tldraw4/pnpm-lock.yaml b/tldraw4/pnpm-lock.yaml index 979c288c..78c64560 100644 --- a/tldraw4/pnpm-lock.yaml +++ b/tldraw4/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@automerge/automerge-repo-react-hooks': specifier: ^2.6.0-subduction.46 version: 2.6.0-subduction.46(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) - '@automerge/react': - specifier: ^2.6.0-subduction.9 - version: 2.6.0-subduction.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) '@inkandswitch/patchwork-bootloader': specifier: ^0.7.2 version: 0.7.2(@automerge/automerge-repo@2.6.0-subduction.46(supports-color@7.2.0))(@automerge/automerge@3.2.5)(@types/react@18.3.28)(react@18.3.1)(supports-color@7.2.0) @@ -105,33 +102,18 @@ importers: packages: - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.12': - resolution: {integrity: sha512-QfO6ZnmWizwalLtf42b0YFMST+AEsFWadoX9vgMLuNHqrNwiFPFI2ypxWfC6O/f+VyB66j00swa5Fu/qyoHUFQ==} - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.48': resolution: {integrity: sha512-fyq/ZqWkrYOuNrQ917KeCaRSuY6fzqH6Q4KCb5czjwKPKO2zFIGKVfK5hZjYsHGWRKuh5BzViaONHn6bJ0Jk4w==} engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.12': - resolution: {integrity: sha512-hoBsrMvxvAXIUWcXFCwBzJvuuvga2AlofWzJegCSBCt4xOnD3XvzJXOLKQsbH/IMhvWxiqWf5l2ZrfOreZruHw==} - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.48': resolution: {integrity: sha512-pf+cmCi/TWQZpqrm+iinVpIM2Ecfzp8JpGEo28SPbE2Q8+eLyO2cz1Dssnuep1+KQ8k7Dfx7aUF9AD7/6jAvTQ==} engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.12': - resolution: {integrity: sha512-h50j3C/cTjPp1+rSZT03tvQlOhroSaCFyhZ7Qibb1Xa3STxVp1vMVKW975SqTiR9aw/Ijn2n18cSsDqCaAFJTg==} - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.48': resolution: {integrity: sha512-TRKAq4iTdrSpOQYxyKZMJDJ5Civa+vONrAHBZkpK+DGShzqBB6vCdTCsrBGBF2Q63H33qQM0XYBQEQKIa/zM+Q==} engines: {node: '>=22.13'} - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.12': - resolution: {integrity: sha512-C3atPZXQVh6AuShxe0GQsmP6tTHQ42T37yqh827yzHjx5gvZohd8GU5DhxJrXfY9MUxbLR8qfajrmqjBhyjsvQ==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.46': resolution: {integrity: sha512-9NXPYE7yh2/e0bIJGsmn+xaWzPK1hat4LU/aV3jfFA2oe+MrGYV3ac8GUmgzB0VFr+djvL8NJaGxUJSO1aOpsg==} engines: {node: '>=22.13'} @@ -139,16 +121,10 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.12': - resolution: {integrity: sha512-Yn2CpVfXk0DHse/cENWMY/Yum4oooDw4XWHwV1tY2oENiURFfSuI9UEyzdgxtXlGPWZrmkNOnbTTytU8rESuWQ==} - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.48': resolution: {integrity: sha512-7XIn/iGrAVS4ugva8m0bU1shhRtuy/fdCAgF6qaIEvbi7XQllLVn3miTTiusLaksNhnRtbMfDLUknazU+vVHew==} engines: {node: '>=22.13'} - '@automerge/automerge-repo@2.6.0-subduction.12': - resolution: {integrity: sha512-yyvifpGsmiEs6fWSkNShEPwwMRn/Tp9ciwPwvYk3+0HQPUXMZ7SFpto1pzAHj1AYxqIOt+J5d28Nt7fIo6z4hQ==} - '@automerge/automerge-repo@2.6.0-subduction.46': resolution: {integrity: sha512-5we6RIe67b6ymRV/+6nKfRJnFD4e8FsudG+haJC/eaYieA4ZBVqVeY4OstJbg4QjkPhQe9VYYWa4HGHSPInYPw==} engines: {node: '>=22.13'} @@ -163,20 +139,12 @@ packages: '@automerge/automerge-subduction@0.16.1': resolution: {integrity: sha512-alH7U4eYn0O7sT6hNLv7CJhNODJi+QD6qfRQTNfZCxL3Q5JZdJ8lFpdvXUkBf+I3bu0GLvVlTaYPWGrBihjpIw==} - '@automerge/automerge-subduction@0.7.0': - resolution: {integrity: sha512-I838jrX+j6zeLEIyszuA246OY1HnTyS+bXODJAVlBmXGM0QzasRePulNrzJtclvowzhY56SybUj4+j/PNCdBhA==} - '@automerge/automerge@3.2.5': resolution: {integrity: sha512-tCdaGDTLaRzBJV+Q9zMyyEH/vUFkBNh21jpwlpcEYV7gs5r63xh9UFa1N3yNJ5ZzmiLBD42TIBeqK+g54sLp0w==} '@automerge/automerge@3.3.2': resolution: {integrity: sha512-9vCdCL7pdQwUra66SBxPVHr+/t9epXKni9KDeak2rNBFMzABVh2u6gSpcwxi3jkR5qr047jVqZv9DlrOfVxLFw==} - '@automerge/react@2.6.0-subduction.12': - resolution: {integrity: sha512-ZYaNmVmk66qd6Oh/AK8BdEV1bg6OK+REeu23CTwfQO8X7MnBxDNeOU17bs5Bdchngdd/jCeBMSdnd55F33XIKA==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - '@babel/code-frame@7.29.0': resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} @@ -2011,9 +1979,6 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - base-x@4.0.1: - resolution: {integrity: sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==} - base-x@5.0.1: resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} @@ -2034,15 +1999,9 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - bs58@5.0.0: - resolution: {integrity: sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==} - bs58@6.0.0: resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} - bs58check@3.0.1: - resolution: {integrity: sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==} - bs58check@4.0.0: resolution: {integrity: sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==} @@ -2707,11 +2666,6 @@ packages: '@types/react': optional: true - react-usestateref@1.0.9: - resolution: {integrity: sha512-t8KLsI7oje0HzfzGhxFXzuwbf1z9vhBM1ptHLUIHhYqZDKFuI5tzdhEVxSNzUkYxwF8XdpOErzHlKxvP7sTERw==} - peerDependencies: - react: '>16.0.0' - react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} @@ -2896,11 +2850,6 @@ packages: resolution: {integrity: sha512-6ZxzVpzDXDa3bJWaHilVayA+BH/1zmxCJoVgvmqJnid/gPoKHxUrS/aC/T6LGQtNHT+XHG9fXPJB4d+IrU30Ew==} hasBin: true - uuid@9.0.1: - resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} - deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). - hasBin: true - vite-node@3.2.4: resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} @@ -3004,18 +2953,6 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} - ws@8.20.0: - resolution: {integrity: sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.21.0: resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} engines: {node: '>=10.0.0'} @@ -3028,9 +2965,6 @@ packages: utf-8-validate: optional: true - xstate@5.30.0: - resolution: {integrity: sha512-mIzIuMjtYVkqXq9dUzYQoag7b/dF1CBS/yhliuPLfR0FwKPC18HiUivb/crcqY2gknhR8gJEhnppLg6ubQ0gGw==} - xstate@5.32.5: resolution: {integrity: sha512-ULazi1oe6wGrXl0Frb6otSlkm5HLifbbVTkMk5kkSKqz4TkxJaVpnl6jOJwKeid3ORPxYyZQgNLUSYX9q65SIA==} @@ -3043,14 +2977,6 @@ packages: snapshots: - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.12(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -3059,16 +2985,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.12(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - debug: 4.4.3(supports-color@7.2.0) - eventemitter3: 5.0.4 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -3078,19 +2994,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.12(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - cbor-x: 1.6.4 - debug: 4.4.3(supports-color@7.2.0) - eventemitter3: 5.0.4 - isomorphic-ws: 5.0.0(ws@8.20.0) - ws: 8.20.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -3103,19 +3006,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)': - dependencies: - '@automerge/automerge': 3.2.5 - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - eventemitter3: 5.0.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-usestateref: 1.0.9(react@18.3.1) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.46(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)': dependencies: '@automerge/automerge': 3.3.2 @@ -3128,14 +3018,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.12(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -3144,24 +3026,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo@2.6.0-subduction.12(supports-color@7.2.0)': - dependencies: - '@automerge/automerge': 3.2.5 - '@automerge/automerge-subduction': 0.7.0 - bs58check: 3.0.1 - cbor-x: 1.6.4 - debug: 4.4.3(supports-color@7.2.0) - eventemitter3: 5.0.4 - fast-sha256: 1.3.0 - isomorphic-ws: 5.0.0(ws@8.20.0) - uuid: 9.0.1 - ws: 8.20.0 - xstate: 5.30.0 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo@2.6.0-subduction.46(supports-color@7.2.0)': dependencies: '@automerge/automerge': 3.3.2 @@ -3202,27 +3066,10 @@ snapshots: '@automerge/automerge-subduction@0.16.1': {} - '@automerge/automerge-subduction@0.7.0': {} - '@automerge/automerge@3.2.5': {} '@automerge/automerge@3.3.2': {} - '@automerge/react@2.6.0-subduction.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.12(supports-color@7.2.0) - '@automerge/automerge-repo-network-broadcastchannel': 2.6.0-subduction.12(supports-color@7.2.0) - '@automerge/automerge-repo-network-messagechannel': 2.6.0-subduction.12(supports-color@7.2.0) - '@automerge/automerge-repo-network-websocket': 2.6.0-subduction.12(supports-color@7.2.0) - '@automerge/automerge-repo-react-hooks': 2.6.0-subduction.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) - '@automerge/automerge-repo-storage-indexeddb': 2.6.0-subduction.12(supports-color@7.2.0) - react: 18.3.1 - transitivePeerDependencies: - - bufferutil - - react-dom - - supports-color - - utf-8-validate - '@babel/code-frame@7.29.0': dependencies: '@babel/helper-validator-identifier': 7.28.5 @@ -5064,8 +4911,6 @@ snapshots: balanced-match@4.0.4: {} - base-x@4.0.1: {} - base-x@5.0.1: {} baseline-browser-mapping@2.10.16: {} @@ -5087,19 +4932,10 @@ snapshots: node-releases: 2.0.37 update-browserslist-db: 1.2.3(browserslist@4.28.2) - bs58@5.0.0: - dependencies: - base-x: 4.0.1 - bs58@6.0.0: dependencies: base-x: 5.0.1 - bs58check@3.0.1: - dependencies: - '@noble/hashes': 1.8.0 - bs58: 5.0.0 - bs58check@4.0.0: dependencies: '@noble/hashes': 1.8.0 @@ -5411,10 +5247,6 @@ snapshots: isexe@2.0.0: {} - isomorphic-ws@5.0.0(ws@8.20.0): - dependencies: - ws: 8.20.0 - isomorphic-ws@5.0.0(ws@8.21.0): dependencies: ws: 8.21.0 @@ -5824,10 +5656,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.28 - react-usestateref@1.0.9(react@18.3.1): - dependencies: - react: 18.3.1 - react@18.3.1: dependencies: loose-envify: 1.4.0 @@ -6016,8 +5844,6 @@ snapshots: uuid@14.0.1: {} - uuid@9.0.1: {} - vite-node@3.2.4(@types/node@24.12.2)(lightningcss@1.32.0)(supports-color@7.2.0): dependencies: cac: 6.7.14 @@ -6116,12 +5942,8 @@ snapshots: word-wrap@1.2.5: {} - ws@8.20.0: {} - ws@8.21.0: {} - xstate@5.30.0: {} - xstate@5.32.5: {} yallist@3.1.1: {} diff --git a/tldraw4/src/PatchworkDocShape.tsx b/tldraw4/src/PatchworkDocShape.tsx index ed745bcb..021112ec 100644 --- a/tldraw4/src/PatchworkDocShape.tsx +++ b/tldraw4/src/PatchworkDocShape.tsx @@ -16,7 +16,7 @@ import { import { useEffect, useRef } from "react"; import { getSupportedToolsForType } from "@inkandswitch/patchwork-plugins"; import type { AutomergeUrl } from "@automerge/automerge-repo/slim"; -import { useDocument } from "@automerge/react"; +import { useDocument } from "@automerge/automerge-repo-react-hooks"; import { automergeUrlToServiceWorkerUrl } from "@inkandswitch/patchwork-filesystem"; // A tldraw shape embedding another Patchwork document via the shared "embed" diff --git a/tldraw4/src/tool.tsx b/tldraw4/src/tool.tsx index ff11cf0e..d2e583b5 100644 --- a/tldraw4/src/tool.tsx +++ b/tldraw4/src/tool.tsx @@ -5,7 +5,7 @@ import { useDocHandle, useDocument, useRepo, -} from "@automerge/react"; +} from "@automerge/automerge-repo-react-hooks"; import { Tldraw, useEditor, diff --git a/tldraw5/package.json b/tldraw5/package.json index 9ad535f4..4355819d 100644 --- a/tldraw5/package.json +++ b/tldraw5/package.json @@ -30,7 +30,6 @@ }, "dependencies": { "@automerge/automerge-repo-react-hooks": "2.6.0-subduction.46", - "@automerge/react": "^2.6.0-subduction.9", "@inkandswitch/patchwork-bootloader": "^0.7.2", "@inkandswitch/patchwork-filesystem": "^0.2.9", "@inkandswitch/patchwork-plugins": "^1.2.5", diff --git a/tldraw5/pnpm-lock.yaml b/tldraw5/pnpm-lock.yaml index 77f9dbce..4900d3be 100644 --- a/tldraw5/pnpm-lock.yaml +++ b/tldraw5/pnpm-lock.yaml @@ -11,9 +11,6 @@ importers: '@automerge/automerge-repo-react-hooks': specifier: 2.6.0-subduction.46 version: 2.6.0-subduction.46(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) - '@automerge/react': - specifier: ^2.6.0-subduction.9 - version: 2.6.0-subduction.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) '@inkandswitch/patchwork-bootloader': specifier: ^0.7.2 version: 0.7.2(@automerge/automerge-repo@2.6.0-subduction.46(supports-color@7.2.0))(@automerge/automerge@3.4.0)(@types/react@18.3.31)(react@18.3.1)(supports-color@7.2.0) @@ -117,26 +114,14 @@ importers: packages: - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.47': - resolution: {integrity: sha512-xqPlvVYtW6Khgoks+jQOc9/M3Cr8XP69cCBjXWXB8D3XUJwJ8Dn6R++s/M+T65fqr+Eeq6njLgWlTYjcFhKU0g==} - engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.48': resolution: {integrity: sha512-fyq/ZqWkrYOuNrQ917KeCaRSuY6fzqH6Q4KCb5czjwKPKO2zFIGKVfK5hZjYsHGWRKuh5BzViaONHn6bJ0Jk4w==} engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.47': - resolution: {integrity: sha512-HT8F4eYwggDsCtWp/WzSzzjRlglR3jqYxsDalKoyu7eKIaD/RuP8emvzqY+OolkDkI0Uau+LizOshvcbhrBRGQ==} - engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.48': resolution: {integrity: sha512-pf+cmCi/TWQZpqrm+iinVpIM2Ecfzp8JpGEo28SPbE2Q8+eLyO2cz1Dssnuep1+KQ8k7Dfx7aUF9AD7/6jAvTQ==} engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.47': - resolution: {integrity: sha512-A8aDy9jizU+6Pb5F6GSqSFNHAAXICib7uV60KHytEZjANL+PU2lMTpAhQnOG15NfJXX3mKXIsQ/duOZIsCRcZA==} - engines: {node: '>=22.13'} - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.48': resolution: {integrity: sha512-TRKAq4iTdrSpOQYxyKZMJDJ5Civa+vONrAHBZkpK+DGShzqBB6vCdTCsrBGBF2Q63H33qQM0XYBQEQKIa/zM+Q==} engines: {node: '>=22.13'} @@ -148,17 +133,6 @@ packages: react: ^18.0.0 || ^19.0.0 react-dom: ^18.0.0 || ^19.0.0 - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.47': - resolution: {integrity: sha512-ZUCQe8Ew6fmHy4IRw1Xf/rAmOxLsJv7JCiFTiofmtMd9SBHmXhgKQuZJJG8KYoVFucRLPZV63vfSPN8k7o+f/w==} - engines: {node: '>=22.13'} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - react-dom: ^18.0.0 || ^19.0.0 - - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.47': - resolution: {integrity: sha512-fYn6nse7jQnVb9EploTsBuYUzW7xFin2HZLneSEQ7w4pRCilIU92ghZEjZSWRKEPunSN0MYEvfJJJdlLyYTdkQ==} - engines: {node: '>=22.13'} - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.48': resolution: {integrity: sha512-7XIn/iGrAVS4ugva8m0bU1shhRtuy/fdCAgF6qaIEvbi7XQllLVn3miTTiusLaksNhnRtbMfDLUknazU+vVHew==} engines: {node: '>=22.13'} @@ -167,10 +141,6 @@ packages: resolution: {integrity: sha512-5we6RIe67b6ymRV/+6nKfRJnFD4e8FsudG+haJC/eaYieA4ZBVqVeY4OstJbg4QjkPhQe9VYYWa4HGHSPInYPw==} engines: {node: '>=22.13'} - '@automerge/automerge-repo@2.6.0-subduction.47': - resolution: {integrity: sha512-NGBQUjGH67Kyrc8a6zoafvDKT1+pnFi2/yiMgULS7l+apWQT2QkgQZ3vQGms/ngdrmM6ye1fhcwHwBn/DGJ6tA==} - engines: {node: '>=22.13'} - '@automerge/automerge-repo@2.6.0-subduction.48': resolution: {integrity: sha512-HNS1YsD0XmQ0vtwIincF7NvEBrQuOMnk+mjCVluRiIpUFvcaVeNAFXCCyBVeXubmg88AHe+LkIQ96v/lyHZ7Wg==} engines: {node: '>=22.13'} @@ -187,12 +157,6 @@ packages: '@automerge/automerge@3.4.0': resolution: {integrity: sha512-THmghtTNGGt2xsI0pM3o1i3PM8oZKcYFgOj25FOzW7l6e94SQOivNtCwy6xc0I8hVJsQSSotoBNs+yk/9hM2dg==} - '@automerge/react@2.6.0-subduction.47': - resolution: {integrity: sha512-3LhGYtuqwfUPWMI5yWen6GDvqzMuPOrlbjeoqdfwQE3/LLG/+c9VBa7VigPciajGWppCYtfR6ohOo1GZoIdf1Q==} - engines: {node: '>=22.13'} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} @@ -2899,14 +2863,6 @@ packages: snapshots: - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.47(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-broadcastchannel@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -2915,15 +2871,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.47(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - eventemitter3: 5.0.4 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-messagechannel@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -2933,18 +2880,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.47(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - cbor-x: 1.6.5 - debug: 4.4.3(supports-color@7.2.0) - eventemitter3: 5.0.4 - ws: 8.21.2 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-network-websocket@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -2969,26 +2904,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo-react-hooks@2.6.0-subduction.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)': - dependencies: - '@automerge/automerge': 3.3.2 - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - eventemitter3: 5.0.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.47(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo-storage-indexeddb@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge-repo': 2.6.0-subduction.48(supports-color@7.2.0) @@ -3015,24 +2930,6 @@ snapshots: - supports-color - utf-8-validate - '@automerge/automerge-repo@2.6.0-subduction.47(supports-color@7.2.0)': - dependencies: - '@automerge/automerge': 3.3.2 - '@automerge/automerge-subduction': 0.16.1 - bs58check: 4.0.0 - cbor-x: 1.6.5 - debug: 4.4.3(supports-color@7.2.0) - eventemitter3: 5.0.4 - fast-sha256: 1.3.0 - isomorphic-ws: 5.0.0(ws@8.21.2) - uuid: 14.0.1 - ws: 8.21.2 - xstate: 5.32.5 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - '@automerge/automerge-repo@2.6.0-subduction.48(supports-color@7.2.0)': dependencies: '@automerge/automerge': 3.3.2 @@ -3059,21 +2956,6 @@ snapshots: '@automerge/automerge@3.4.0': {} - '@automerge/react@2.6.0-subduction.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0)': - dependencies: - '@automerge/automerge-repo': 2.6.0-subduction.47(supports-color@7.2.0) - '@automerge/automerge-repo-network-broadcastchannel': 2.6.0-subduction.47(supports-color@7.2.0) - '@automerge/automerge-repo-network-messagechannel': 2.6.0-subduction.47(supports-color@7.2.0) - '@automerge/automerge-repo-network-websocket': 2.6.0-subduction.47(supports-color@7.2.0) - '@automerge/automerge-repo-react-hooks': 2.6.0-subduction.47(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(supports-color@7.2.0) - '@automerge/automerge-repo-storage-indexeddb': 2.6.0-subduction.47(supports-color@7.2.0) - react: 18.3.1 - transitivePeerDependencies: - - bufferutil - - react-dom - - supports-color - - utf-8-validate - '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 diff --git a/tldraw5/src/PatchworkDocShape.tsx b/tldraw5/src/PatchworkDocShape.tsx index cd3bdb41..75450463 100644 --- a/tldraw5/src/PatchworkDocShape.tsx +++ b/tldraw5/src/PatchworkDocShape.tsx @@ -22,7 +22,7 @@ import { type LoadedTool, } from "@inkandswitch/patchwork-plugins"; import type { AutomergeUrl } from "@automerge/automerge-repo/slim"; -import { useDocument, useRepo } from "@automerge/react"; +import { useDocument, useRepo } from "@automerge/automerge-repo-react-hooks"; import { automergeUrlToServiceWorkerUrl } from "@inkandswitch/patchwork-filesystem"; // A tldraw shape that embeds another Patchwork document, rendered via the diff --git a/tldraw5/src/file-io.tsx b/tldraw5/src/file-io.tsx index 31054f21..b68f1bbb 100644 --- a/tldraw5/src/file-io.tsx +++ b/tldraw5/src/file-io.tsx @@ -4,7 +4,8 @@ // writes the current document back out as an archive. import { createContext, useContext, type FC, type ReactNode } from "react"; -import { useRepo, type DocHandle, type Repo } from "@automerge/react"; +import { useRepo } from "@automerge/automerge-repo-react-hooks"; +import type { DocHandle, Repo } from "@automerge/automerge-repo/slim"; import { DefaultMainMenu, DefaultMainMenuContent, diff --git a/tldraw5/src/migrate.tsx b/tldraw5/src/migrate.tsx index 89c568d7..f637447a 100644 --- a/tldraw5/src/migrate.tsx +++ b/tldraw5/src/migrate.tsx @@ -14,7 +14,7 @@ import { getRegistry, type DatatypeDescription, } from "@inkandswitch/patchwork-plugins"; -import type { DocHandle } from "@automerge/react"; +import type { DocHandle } from "@automerge/automerge-repo/slim"; import type { TLDrawDoc } from "./datatype.ts"; import { PatchworkDocShapeUtil } from "./PatchworkDocShape.tsx"; diff --git a/tldraw5/src/script.ts b/tldraw5/src/script.ts index bf7e15ac..4342ca75 100644 --- a/tldraw5/src/script.ts +++ b/tldraw5/src/script.ts @@ -9,7 +9,7 @@ // documents rather than paths, so each file is rewritten and handed to the // browser as a blob module. -import type { DocHandle, Repo } from "@automerge/react"; +import type { DocHandle, Repo } from "@automerge/automerge-repo/slim"; import type { DocLink, FolderDoc, diff --git a/tldraw5/src/tool.tsx b/tldraw5/src/tool.tsx index 67651e20..014768c8 100644 --- a/tldraw5/src/tool.tsx +++ b/tldraw5/src/tool.tsx @@ -1,12 +1,11 @@ -import type { AutomergeUrl, DocHandle } from "@automerge/automerge-repo/slim"; +import type { AutomergeUrl, DocHandle, Repo } from "@automerge/automerge-repo/slim"; import { decodeHeads, type UrlHeads } from "@automerge/automerge-repo/slim"; import { RepoContext, useDocHandle, useDocument, useRepo, - type Repo, -} from "@automerge/react"; +} from "@automerge/automerge-repo-react-hooks"; import { Tldraw, useEditor,