From 396bd4b5b5bdb664cf8869e6caa17f5bde11b2d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:51:54 +0000 Subject: [PATCH] Version Packages --- .changeset/classic-sync-in-the-tab.md | 8 ---- .changeset/drop-package-memory.md | 6 --- .changeset/every-tab-its-own-node.md | 13 ------ .changeset/hashed-worker-files.md | 21 --------- .changeset/shrink-automerge-repo-patch.md | 5 --- .changeset/tabs-resolve-handoffs.md | 12 ------ .changeset/worker-recycle-fixes.md | 9 ---- .changeset/worker-repo-on-self.md | 5 --- core/bootloader/CHANGELOG.md | 52 +++++++++++++++++++++++ core/bootloader/package.json | 2 +- core/filesystem/CHANGELOG.md | 13 ++++++ core/filesystem/package.json | 2 +- core/patchwork/CHANGELOG.md | 51 ++++++++++++++++++++++ core/patchwork/package.json | 2 +- packages/e2e/CHANGELOG.md | 20 +++++++++ packages/e2e/package.json | 2 +- 16 files changed, 140 insertions(+), 83 deletions(-) delete mode 100644 .changeset/classic-sync-in-the-tab.md delete mode 100644 .changeset/drop-package-memory.md delete mode 100644 .changeset/every-tab-its-own-node.md delete mode 100644 .changeset/hashed-worker-files.md delete mode 100644 .changeset/shrink-automerge-repo-patch.md delete mode 100644 .changeset/tabs-resolve-handoffs.md delete mode 100644 .changeset/worker-recycle-fixes.md delete mode 100644 .changeset/worker-repo-on-self.md diff --git a/.changeset/classic-sync-in-the-tab.md b/.changeset/classic-sync-in-the-tab.md deleted file mode 100644 index f48fda91a..000000000 --- a/.changeset/classic-sync-in-the-tab.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch -"@inkandswitch/patchwork": patch ---- - -Classic automerge-repo sync connects from the tab's own Repo instead of the automerge worker, so a worker restart no longer drops it and tabs no longer ask the replacement to connect again. - -`connectClassicSync` in `@inkandswitch/patchwork-bootloader` now takes the Repo: `connectClassicSync(repo, server?)`. `setupServiceWorker()` no longer returns `connectClassicSync`. `patchwork.sw.connectClassicSync(server?)` works as before. The `ConnectClassicSyncMessage` type is gone. diff --git a/.changeset/drop-package-memory.md b/.changeset/drop-package-memory.md deleted file mode 100644 index 01cfc5026..000000000 --- a/.changeset/drop-package-memory.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch -"@inkandswitch/patchwork-filesystem": patch ---- - -The automerge worker no longer measures package memory. Removed: the `@patchwork/package-memory` channel and its message types (`PACKAGE_MEMORY_CHANNEL`, `PackageMemoryRequestMessage`, `PackageMemoryDocument`, `PackageMemoryAcceptedMessage`, `PackageMemoryReplyMessage`), and the records a restarting worker saved for its replacement (`LOADED_CACHE`). The service worker clears that cache along with every other cache but its own. `packageDocuments` is gone from `@inkandswitch/patchwork-filesystem`; nothing else walked a package. diff --git a/.changeset/every-tab-its-own-node.md b/.changeset/every-tab-its-own-node.md deleted file mode 100644 index bd1b3dc00..000000000 --- a/.changeset/every-tab-its-own-node.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch -"@inkandswitch/patchwork": patch -"@inkandswitch/patchwork-filesystem": patch ---- - -Every tab is its own Subduction node again, with its own socket to the sync server. The sync node in the automerge worker is gone. When the worker restarts (to recycle its wasm memory, for a new build, or after a trap), it takes only its resolver Repo with it. Before, every restart threw away the node every tab synced through, and each tab had to reconnect and resubscribe. The worker's resolver Repo has its own socket too. - -The automerge worker still holds `AUTOMERGE_WORKER_LOCK` for as long as it lives. The lock now does one thing: it lets tabs and the service worker tell when the worker has died. A replacement no longer waits for it before it builds its Repo, answers handoffs or announces itself `online`. An instance that's restarting already stops taking handoffs and package-memory requests, so the two don't answer at once. - -Removed from `@inkandswitch/patchwork-bootloader`: `getSyncRemote`, `watchDocument`, `relayKeyhive` and the `PortSyncRemote` type. Pass `subductionWebsocketEndpoints: [syncServerUrl]` to the Repo instead of `subductionRemote: getSyncRemote()`. On keyhive sites, use `new Repo(config)` directly rather than wrapping it in `relayKeyhive`. `loadKeyhiveSigner` is gone from `@inkandswitch/patchwork-bootloader/signer`. `startWorkerControl` no longer returns `holding`. - -Tabs still follow package docs without loading them. `watchDocument(repo, url, onChange)`, now exported from `@inkandswitch/patchwork`, subscribes to the document through the tab's own Subduction connection and listens on the origin's heads channel. Every Repo relays the server's heads there, so the watcher hears both the server's pushes and other tabs' writes. `onChange(seen)` gets the same summary `ModuleWatcher` already compares, so a repeat costs no lookup. Subduction can't unsubscribe from a single document, so after `unwatch()` the server keeps pushing it to that tab until the connection drops. diff --git a/.changeset/hashed-worker-files.md b/.changeset/hashed-worker-files.md deleted file mode 100644 index b76d7b661..000000000 --- a/.changeset/hashed-worker-files.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -"@inkandswitch/patchwork": patch -"@inkandswitch/patchwork-bootloader": patch -"@inkandswitch/patchwork-e2e": patch ---- - -A build emits every file the automerge worker and the page load under a content-hashed name in `/assets`, served `immutable`, instead of at a fixed path: the `/packages/…` chunks, `automerge.wasm`, `subduction.wasm`, `keyhive_wasm.wasm`, the worker's second stage (`automerge-protocol-handler.js`) and `module-loader-worker.js`. A fixed path could only be revalidated, which fails offline, or cached on a guess, which after a deploy could start the worker on one build's files and the rest of another's. Now a build only ever loads its own copies, and they start offline from the HTTP cache. - -Only `service-worker.js` and the automerge worker's script, `automerge-protocol-handler-worker.js`, keep their fixed paths, since the browser starts both by URL from one build to the next. The worker's script is served `max-age=0, stale-while-revalidate=31536000`, so the browser starts it from its copy when it can't check for a newer one. Everything that copy imports is hashed, so even a copy from an older build brings up one whole build. A worker that restarts because a newer tab connected fetches its script past the cache first, so the replacement starts on the newer build rather than the cached one, which would carry on serving the newer tab. - -The fixed paths still work: the generated `_redirects` sends each one to its hashed name with a 302, and `vite preview` does the same, whether or not the site writes Netlify files. But a module imported both by a fixed path and by its hashed name is two modules, so code should import the packages by bare specifier (the import map points at the hashed names) and load the rest by the URLs in `@inkandswitch/patchwork-bootloader/emitted`: `AUTOMERGE_WASM`, `SUBDUCTION_WASM`, `KEYHIVE_WASM`, `AUTOMERGE_PROTOCOL_HANDLER` and `MODULE_LOADER_WORKER`. In dev these are the fixed paths. - -API changes: - -- `emitWasmAssets` (`@inkandswitch/patchwork-bootloader/externals`) emits the wasm under hashed names and returns each one's reference id by file name. -- `@inkandswitch/patchwork/vite` has a `hashed()` plugin, included in `patchwork()`, which swaps in the hashed names. `hashedFiles(config)` gives a build's table of them, also exported from `@inkandswitch/patchwork/vite/hashed`. -- `workers` entries say whether a worker is `hashed`. The workers no longer import the packages from `/packages/…` URLs: they link to the build's chunks like the page does. -- `buildHeaders(options, hashed?)` takes that table for the wasm preloads. -- The new `buildRedirects(hashed?)` writes the redirects. `REVALIDATED_PATHS` and `REVALIDATED_CACHE_CONTROL` name the stale-while-revalidate files and their header. - -The e2e offline test waits for the wasm by the URLs the page preloads. diff --git a/.changeset/shrink-automerge-repo-patch.md b/.changeset/shrink-automerge-repo-patch.md deleted file mode 100644 index 6cd7fd193..000000000 --- a/.changeset/shrink-automerge-repo-patch.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@inkandswitch/patchwork": patch ---- - -The shipped automerge-repo patch no longer adds `subductionRemote`, the option that let a Repo sync through another node; nothing uses it since every tab became its own node again. The `SubductionRemote`, `SubductionRemoteHandlers` and `SubductionRemoteSyncResult` types are gone, as are the `SubductionStorageBridge`, `INTERCEPTOR_PREFIX` and `SubductionConnections` exports. `subductionSaveThrottle` stays. A site applying the patch from `@inkandswitch/patchwork/dist/patches` should take the new one. diff --git a/.changeset/tabs-resolve-handoffs.md b/.changeset/tabs-resolve-handoffs.md deleted file mode 100644 index 5a3de7f2e..000000000 --- a/.changeset/tabs-resolve-handoffs.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch -"@inkandswitch/patchwork": patch ---- - -The service worker asks a tab to resolve `automerge:` requests before falling back to the automerge worker. Every tab is its own node with its own Repo, so the tab that made a request can usually answer it from documents it already holds. - -The service worker asks the tab that made the request first, then any other open tab (focused first, then visible). This covers requests from the module-loader worker and navigations, which have no window of their own. Only when no tab can answer does it broadcast to the automerge worker, which now handles just those requests. - -The tab replies `accepted` straight away, or `unavailable` if it has no Repo yet. A tab that says nothing within 250ms is passed over. That number is provisional: the service worker logs each tab's accept latency under `patchwork:serviceworker`, to measure from. A tab that has accepted holds a Web Lock for that one request until it has replied. If the lock is released with no reply, the tab has gone (closed, crashed, navigated away) and the next candidate is asked. - -New in `@inkandswitch/patchwork-bootloader`: `resolveHandoffsWith(repo)` makes this tab answer with `repo`. `@inkandswitch/patchwork` calls it with the Repo it creates; a site that passes its own Repo isn't asked and keeps resolving through the automerge worker. The resolving code the worker used is shared by both, in `resolver.ts`. The new messages are `ResolveRequestMessage`, `ResolveAcceptedMessage` and `ResolveUnavailableMessage`. diff --git a/.changeset/worker-recycle-fixes.md b/.changeset/worker-recycle-fixes.md deleted file mode 100644 index 70ca4a592..000000000 --- a/.changeset/worker-recycle-fixes.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch ---- - -Fixes to how the automerge worker recycles itself to give back wasm memory: - -- The service worker no longer deletes the cache a restarting automerge worker saves what it had loaded into (`patchwork-package-memory`, now `LOADED_CACHE` in the bootloader's types). It cleared every cache but its own on each activation, so the replacement never had anything to restore. -- A worker with classic sync connected recycles like any other. Tabs ask the replacement to connect classic sync again, after a restart and after the worker goes unexpectedly, so connecting it no longer stops the worker from ever recycling. -- The recycle check counts subduction's wasm memory too, not only automerge's. diff --git a/.changeset/worker-repo-on-self.md b/.changeset/worker-repo-on-self.md deleted file mode 100644 index 29f845db8..000000000 --- a/.changeset/worker-repo-on-self.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"@inkandswitch/patchwork-bootloader": patch ---- - -The automerge worker's resolver Repo is on `self.repo` again from the moment the worker starts, for the console. The Repo is only built once something has to be resolved, so until then reading `self.repo` starts building it and gives its promise. `await repo` works either way. diff --git a/core/bootloader/CHANGELOG.md b/core/bootloader/CHANGELOG.md index 039e30d53..5713127a3 100644 --- a/core/bootloader/CHANGELOG.md +++ b/core/bootloader/CHANGELOG.md @@ -1,5 +1,57 @@ # @inkandswitch/patchwork-bootloader +## 0.8.7 + +### Patch Changes + +- bedd49b: Classic automerge-repo sync connects from the tab's own Repo instead of the automerge worker, so a worker restart no longer drops it and tabs no longer ask the replacement to connect again. + + `connectClassicSync` in `@inkandswitch/patchwork-bootloader` now takes the Repo: `connectClassicSync(repo, server?)`. `setupServiceWorker()` no longer returns `connectClassicSync`. `patchwork.sw.connectClassicSync(server?)` works as before. The `ConnectClassicSyncMessage` type is gone. + +- bedd49b: The automerge worker no longer measures package memory. Removed: the `@patchwork/package-memory` channel and its message types (`PACKAGE_MEMORY_CHANNEL`, `PackageMemoryRequestMessage`, `PackageMemoryDocument`, `PackageMemoryAcceptedMessage`, `PackageMemoryReplyMessage`), and the records a restarting worker saved for its replacement (`LOADED_CACHE`). The service worker clears that cache along with every other cache but its own. `packageDocuments` is gone from `@inkandswitch/patchwork-filesystem`; nothing else walked a package. +- c9d2f39: Every tab is its own Subduction node again, with its own socket to the sync server. The sync node in the automerge worker is gone. When the worker restarts (to recycle its wasm memory, for a new build, or after a trap), it takes only its resolver Repo with it. Before, every restart threw away the node every tab synced through, and each tab had to reconnect and resubscribe. The worker's resolver Repo has its own socket too. + + The automerge worker still holds `AUTOMERGE_WORKER_LOCK` for as long as it lives. The lock now does one thing: it lets tabs and the service worker tell when the worker has died. A replacement no longer waits for it before it builds its Repo, answers handoffs or announces itself `online`. An instance that's restarting already stops taking handoffs and package-memory requests, so the two don't answer at once. + + Removed from `@inkandswitch/patchwork-bootloader`: `getSyncRemote`, `watchDocument`, `relayKeyhive` and the `PortSyncRemote` type. Pass `subductionWebsocketEndpoints: [syncServerUrl]` to the Repo instead of `subductionRemote: getSyncRemote()`. On keyhive sites, use `new Repo(config)` directly rather than wrapping it in `relayKeyhive`. `loadKeyhiveSigner` is gone from `@inkandswitch/patchwork-bootloader/signer`. `startWorkerControl` no longer returns `holding`. + + Tabs still follow package docs without loading them. `watchDocument(repo, url, onChange)`, now exported from `@inkandswitch/patchwork`, subscribes to the document through the tab's own Subduction connection and listens on the origin's heads channel. Every Repo relays the server's heads there, so the watcher hears both the server's pushes and other tabs' writes. `onChange(seen)` gets the same summary `ModuleWatcher` already compares, so a repeat costs no lookup. Subduction can't unsubscribe from a single document, so after `unwatch()` the server keeps pushing it to that tab until the connection drops. + +- a12981b: A build emits every file the automerge worker and the page load under a content-hashed name in `/assets`, served `immutable`, instead of at a fixed path: the `/packages/…` chunks, `automerge.wasm`, `subduction.wasm`, `keyhive_wasm.wasm`, the worker's second stage (`automerge-protocol-handler.js`) and `module-loader-worker.js`. A fixed path could only be revalidated, which fails offline, or cached on a guess, which after a deploy could start the worker on one build's files and the rest of another's. Now a build only ever loads its own copies, and they start offline from the HTTP cache. + + Only `service-worker.js` and the automerge worker's script, `automerge-protocol-handler-worker.js`, keep their fixed paths, since the browser starts both by URL from one build to the next. The worker's script is served `max-age=0, stale-while-revalidate=31536000`, so the browser starts it from its copy when it can't check for a newer one. Everything that copy imports is hashed, so even a copy from an older build brings up one whole build. A worker that restarts because a newer tab connected fetches its script past the cache first, so the replacement starts on the newer build rather than the cached one, which would carry on serving the newer tab. + + The fixed paths still work: the generated `_redirects` sends each one to its hashed name with a 302, and `vite preview` does the same, whether or not the site writes Netlify files. But a module imported both by a fixed path and by its hashed name is two modules, so code should import the packages by bare specifier (the import map points at the hashed names) and load the rest by the URLs in `@inkandswitch/patchwork-bootloader/emitted`: `AUTOMERGE_WASM`, `SUBDUCTION_WASM`, `KEYHIVE_WASM`, `AUTOMERGE_PROTOCOL_HANDLER` and `MODULE_LOADER_WORKER`. In dev these are the fixed paths. + + API changes: + + - `emitWasmAssets` (`@inkandswitch/patchwork-bootloader/externals`) emits the wasm under hashed names and returns each one's reference id by file name. + - `@inkandswitch/patchwork/vite` has a `hashed()` plugin, included in `patchwork()`, which swaps in the hashed names. `hashedFiles(config)` gives a build's table of them, also exported from `@inkandswitch/patchwork/vite/hashed`. + - `workers` entries say whether a worker is `hashed`. The workers no longer import the packages from `/packages/…` URLs: they link to the build's chunks like the page does. + - `buildHeaders(options, hashed?)` takes that table for the wasm preloads. + - The new `buildRedirects(hashed?)` writes the redirects. `REVALIDATED_PATHS` and `REVALIDATED_CACHE_CONTROL` name the stale-while-revalidate files and their header. + + The e2e offline test waits for the wasm by the URLs the page preloads. + +- bedd49b: The service worker asks a tab to resolve `automerge:` requests before falling back to the automerge worker. Every tab is its own node with its own Repo, so the tab that made a request can usually answer it from documents it already holds. + + The service worker asks the tab that made the request first, then any other open tab (focused first, then visible). This covers requests from the module-loader worker and navigations, which have no window of their own. Only when no tab can answer does it broadcast to the automerge worker, which now handles just those requests. + + The tab replies `accepted` straight away, or `unavailable` if it has no Repo yet. A tab that says nothing within 250ms is passed over. That number is provisional: the service worker logs each tab's accept latency under `patchwork:serviceworker`, to measure from. A tab that has accepted holds a Web Lock for that one request until it has replied. If the lock is released with no reply, the tab has gone (closed, crashed, navigated away) and the next candidate is asked. + + New in `@inkandswitch/patchwork-bootloader`: `resolveHandoffsWith(repo)` makes this tab answer with `repo`. `@inkandswitch/patchwork` calls it with the Repo it creates; a site that passes its own Repo isn't asked and keeps resolving through the automerge worker. The resolving code the worker used is shared by both, in `resolver.ts`. The new messages are `ResolveRequestMessage`, `ResolveAcceptedMessage` and `ResolveUnavailableMessage`. + +- a12981b: Fixes to how the automerge worker recycles itself to give back wasm memory: + + - The service worker no longer deletes the cache a restarting automerge worker saves what it had loaded into (`patchwork-package-memory`, now `LOADED_CACHE` in the bootloader's types). It cleared every cache but its own on each activation, so the replacement never had anything to restore. + - A worker with classic sync connected recycles like any other. Tabs ask the replacement to connect classic sync again, after a restart and after the worker goes unexpectedly, so connecting it no longer stops the worker from ever recycling. + - The recycle check counts subduction's wasm memory too, not only automerge's. + +- a12981b: The automerge worker's resolver Repo is on `self.repo` again from the moment the worker starts, for the console. The Repo is only built once something has to be resolved, so until then reading `self.repo` starts building it and gives its promise. `await repo` works either way. +- Updated dependencies [bedd49b] +- Updated dependencies [c9d2f39] + - @inkandswitch/patchwork-filesystem@0.2.16 + ## 0.8.6 ### Patch Changes diff --git a/core/bootloader/package.json b/core/bootloader/package.json index 0ecfe9646..207a095d6 100644 --- a/core/bootloader/package.json +++ b/core/bootloader/package.json @@ -5,7 +5,7 @@ "url": "git+https://github.com/inkandswitch/patchwork-system.git", "directory": "core/bootloader" }, - "version": "0.8.6", + "version": "0.8.7", "author": "chee", "type": "module", "license": "MIT", diff --git a/core/filesystem/CHANGELOG.md b/core/filesystem/CHANGELOG.md index 408668b55..47c0f4a64 100644 --- a/core/filesystem/CHANGELOG.md +++ b/core/filesystem/CHANGELOG.md @@ -1,5 +1,18 @@ # @inkandswitch/patchwork-filesystem +## 0.2.16 + +### Patch Changes + +- bedd49b: The automerge worker no longer measures package memory. Removed: the `@patchwork/package-memory` channel and its message types (`PACKAGE_MEMORY_CHANNEL`, `PackageMemoryRequestMessage`, `PackageMemoryDocument`, `PackageMemoryAcceptedMessage`, `PackageMemoryReplyMessage`), and the records a restarting worker saved for its replacement (`LOADED_CACHE`). The service worker clears that cache along with every other cache but its own. `packageDocuments` is gone from `@inkandswitch/patchwork-filesystem`; nothing else walked a package. +- c9d2f39: Every tab is its own Subduction node again, with its own socket to the sync server. The sync node in the automerge worker is gone. When the worker restarts (to recycle its wasm memory, for a new build, or after a trap), it takes only its resolver Repo with it. Before, every restart threw away the node every tab synced through, and each tab had to reconnect and resubscribe. The worker's resolver Repo has its own socket too. + + The automerge worker still holds `AUTOMERGE_WORKER_LOCK` for as long as it lives. The lock now does one thing: it lets tabs and the service worker tell when the worker has died. A replacement no longer waits for it before it builds its Repo, answers handoffs or announces itself `online`. An instance that's restarting already stops taking handoffs and package-memory requests, so the two don't answer at once. + + Removed from `@inkandswitch/patchwork-bootloader`: `getSyncRemote`, `watchDocument`, `relayKeyhive` and the `PortSyncRemote` type. Pass `subductionWebsocketEndpoints: [syncServerUrl]` to the Repo instead of `subductionRemote: getSyncRemote()`. On keyhive sites, use `new Repo(config)` directly rather than wrapping it in `relayKeyhive`. `loadKeyhiveSigner` is gone from `@inkandswitch/patchwork-bootloader/signer`. `startWorkerControl` no longer returns `holding`. + + Tabs still follow package docs without loading them. `watchDocument(repo, url, onChange)`, now exported from `@inkandswitch/patchwork`, subscribes to the document through the tab's own Subduction connection and listens on the origin's heads channel. Every Repo relays the server's heads there, so the watcher hears both the server's pushes and other tabs' writes. `onChange(seen)` gets the same summary `ModuleWatcher` already compares, so a repeat costs no lookup. Subduction can't unsubscribe from a single document, so after `unwatch()` the server keeps pushing it to that tab until the connection drops. + ## 0.2.15 ### Patch Changes diff --git a/core/filesystem/package.json b/core/filesystem/package.json index a28716fc9..7f4b71120 100644 --- a/core/filesystem/package.json +++ b/core/filesystem/package.json @@ -6,7 +6,7 @@ "directory": "core/filesystem" }, "type": "module", - "version": "0.2.15", + "version": "0.2.16", "author": "Ink & Switch", "description": "", "main": "dist/index.js", diff --git a/core/patchwork/CHANGELOG.md b/core/patchwork/CHANGELOG.md index 0e4f3eda8..235620614 100644 --- a/core/patchwork/CHANGELOG.md +++ b/core/patchwork/CHANGELOG.md @@ -1,5 +1,56 @@ # @inkandswitch/patchwork +## 0.8.8 + +### Patch Changes + +- bedd49b: Classic automerge-repo sync connects from the tab's own Repo instead of the automerge worker, so a worker restart no longer drops it and tabs no longer ask the replacement to connect again. + + `connectClassicSync` in `@inkandswitch/patchwork-bootloader` now takes the Repo: `connectClassicSync(repo, server?)`. `setupServiceWorker()` no longer returns `connectClassicSync`. `patchwork.sw.connectClassicSync(server?)` works as before. The `ConnectClassicSyncMessage` type is gone. + +- c9d2f39: Every tab is its own Subduction node again, with its own socket to the sync server. The sync node in the automerge worker is gone. When the worker restarts (to recycle its wasm memory, for a new build, or after a trap), it takes only its resolver Repo with it. Before, every restart threw away the node every tab synced through, and each tab had to reconnect and resubscribe. The worker's resolver Repo has its own socket too. + + The automerge worker still holds `AUTOMERGE_WORKER_LOCK` for as long as it lives. The lock now does one thing: it lets tabs and the service worker tell when the worker has died. A replacement no longer waits for it before it builds its Repo, answers handoffs or announces itself `online`. An instance that's restarting already stops taking handoffs and package-memory requests, so the two don't answer at once. + + Removed from `@inkandswitch/patchwork-bootloader`: `getSyncRemote`, `watchDocument`, `relayKeyhive` and the `PortSyncRemote` type. Pass `subductionWebsocketEndpoints: [syncServerUrl]` to the Repo instead of `subductionRemote: getSyncRemote()`. On keyhive sites, use `new Repo(config)` directly rather than wrapping it in `relayKeyhive`. `loadKeyhiveSigner` is gone from `@inkandswitch/patchwork-bootloader/signer`. `startWorkerControl` no longer returns `holding`. + + Tabs still follow package docs without loading them. `watchDocument(repo, url, onChange)`, now exported from `@inkandswitch/patchwork`, subscribes to the document through the tab's own Subduction connection and listens on the origin's heads channel. Every Repo relays the server's heads there, so the watcher hears both the server's pushes and other tabs' writes. `onChange(seen)` gets the same summary `ModuleWatcher` already compares, so a repeat costs no lookup. Subduction can't unsubscribe from a single document, so after `unwatch()` the server keeps pushing it to that tab until the connection drops. + +- a12981b: A build emits every file the automerge worker and the page load under a content-hashed name in `/assets`, served `immutable`, instead of at a fixed path: the `/packages/…` chunks, `automerge.wasm`, `subduction.wasm`, `keyhive_wasm.wasm`, the worker's second stage (`automerge-protocol-handler.js`) and `module-loader-worker.js`. A fixed path could only be revalidated, which fails offline, or cached on a guess, which after a deploy could start the worker on one build's files and the rest of another's. Now a build only ever loads its own copies, and they start offline from the HTTP cache. + + Only `service-worker.js` and the automerge worker's script, `automerge-protocol-handler-worker.js`, keep their fixed paths, since the browser starts both by URL from one build to the next. The worker's script is served `max-age=0, stale-while-revalidate=31536000`, so the browser starts it from its copy when it can't check for a newer one. Everything that copy imports is hashed, so even a copy from an older build brings up one whole build. A worker that restarts because a newer tab connected fetches its script past the cache first, so the replacement starts on the newer build rather than the cached one, which would carry on serving the newer tab. + + The fixed paths still work: the generated `_redirects` sends each one to its hashed name with a 302, and `vite preview` does the same, whether or not the site writes Netlify files. But a module imported both by a fixed path and by its hashed name is two modules, so code should import the packages by bare specifier (the import map points at the hashed names) and load the rest by the URLs in `@inkandswitch/patchwork-bootloader/emitted`: `AUTOMERGE_WASM`, `SUBDUCTION_WASM`, `KEYHIVE_WASM`, `AUTOMERGE_PROTOCOL_HANDLER` and `MODULE_LOADER_WORKER`. In dev these are the fixed paths. + + API changes: + + - `emitWasmAssets` (`@inkandswitch/patchwork-bootloader/externals`) emits the wasm under hashed names and returns each one's reference id by file name. + - `@inkandswitch/patchwork/vite` has a `hashed()` plugin, included in `patchwork()`, which swaps in the hashed names. `hashedFiles(config)` gives a build's table of them, also exported from `@inkandswitch/patchwork/vite/hashed`. + - `workers` entries say whether a worker is `hashed`. The workers no longer import the packages from `/packages/…` URLs: they link to the build's chunks like the page does. + - `buildHeaders(options, hashed?)` takes that table for the wasm preloads. + - The new `buildRedirects(hashed?)` writes the redirects. `REVALIDATED_PATHS` and `REVALIDATED_CACHE_CONTROL` name the stale-while-revalidate files and their header. + + The e2e offline test waits for the wasm by the URLs the page preloads. + +- bedd49b: The shipped automerge-repo patch no longer adds `subductionRemote`, the option that let a Repo sync through another node; nothing uses it since every tab became its own node again. The `SubductionRemote`, `SubductionRemoteHandlers` and `SubductionRemoteSyncResult` types are gone, as are the `SubductionStorageBridge`, `INTERCEPTOR_PREFIX` and `SubductionConnections` exports. `subductionSaveThrottle` stays. A site applying the patch from `@inkandswitch/patchwork/dist/patches` should take the new one. +- bedd49b: The service worker asks a tab to resolve `automerge:` requests before falling back to the automerge worker. Every tab is its own node with its own Repo, so the tab that made a request can usually answer it from documents it already holds. + + The service worker asks the tab that made the request first, then any other open tab (focused first, then visible). This covers requests from the module-loader worker and navigations, which have no window of their own. Only when no tab can answer does it broadcast to the automerge worker, which now handles just those requests. + + The tab replies `accepted` straight away, or `unavailable` if it has no Repo yet. A tab that says nothing within 250ms is passed over. That number is provisional: the service worker logs each tab's accept latency under `patchwork:serviceworker`, to measure from. A tab that has accepted holds a Web Lock for that one request until it has replied. If the lock is released with no reply, the tab has gone (closed, crashed, navigated away) and the next candidate is asked. + + New in `@inkandswitch/patchwork-bootloader`: `resolveHandoffsWith(repo)` makes this tab answer with `repo`. `@inkandswitch/patchwork` calls it with the Repo it creates; a site that passes its own Repo isn't asked and keeps resolving through the automerge worker. The resolving code the worker used is shared by both, in `resolver.ts`. The new messages are `ResolveRequestMessage`, `ResolveAcceptedMessage` and `ResolveUnavailableMessage`. + +- Updated dependencies [bedd49b] +- Updated dependencies [bedd49b] +- Updated dependencies [c9d2f39] +- Updated dependencies [a12981b] +- Updated dependencies [bedd49b] +- Updated dependencies [a12981b] +- Updated dependencies [a12981b] + - @inkandswitch/patchwork-bootloader@0.8.7 + - @inkandswitch/patchwork-filesystem@0.2.16 + ## 0.8.7 ### Patch Changes diff --git a/core/patchwork/package.json b/core/patchwork/package.json index 8a00186cb..fb72cda13 100644 --- a/core/patchwork/package.json +++ b/core/patchwork/package.json @@ -5,7 +5,7 @@ "url": "git+https://github.com/inkandswitch/patchwork-system.git", "directory": "core/patchwork" }, - "version": "0.8.7", + "version": "0.8.8", "author": "Ink & Switch", "type": "module", "license": "MIT", diff --git a/packages/e2e/CHANGELOG.md b/packages/e2e/CHANGELOG.md index 8cde524b3..8890a9b61 100644 --- a/packages/e2e/CHANGELOG.md +++ b/packages/e2e/CHANGELOG.md @@ -1,5 +1,25 @@ # @inkandswitch/patchwork-e2e +## 0.2.4 + +### Patch Changes + +- a12981b: A build emits every file the automerge worker and the page load under a content-hashed name in `/assets`, served `immutable`, instead of at a fixed path: the `/packages/…` chunks, `automerge.wasm`, `subduction.wasm`, `keyhive_wasm.wasm`, the worker's second stage (`automerge-protocol-handler.js`) and `module-loader-worker.js`. A fixed path could only be revalidated, which fails offline, or cached on a guess, which after a deploy could start the worker on one build's files and the rest of another's. Now a build only ever loads its own copies, and they start offline from the HTTP cache. + + Only `service-worker.js` and the automerge worker's script, `automerge-protocol-handler-worker.js`, keep their fixed paths, since the browser starts both by URL from one build to the next. The worker's script is served `max-age=0, stale-while-revalidate=31536000`, so the browser starts it from its copy when it can't check for a newer one. Everything that copy imports is hashed, so even a copy from an older build brings up one whole build. A worker that restarts because a newer tab connected fetches its script past the cache first, so the replacement starts on the newer build rather than the cached one, which would carry on serving the newer tab. + + The fixed paths still work: the generated `_redirects` sends each one to its hashed name with a 302, and `vite preview` does the same, whether or not the site writes Netlify files. But a module imported both by a fixed path and by its hashed name is two modules, so code should import the packages by bare specifier (the import map points at the hashed names) and load the rest by the URLs in `@inkandswitch/patchwork-bootloader/emitted`: `AUTOMERGE_WASM`, `SUBDUCTION_WASM`, `KEYHIVE_WASM`, `AUTOMERGE_PROTOCOL_HANDLER` and `MODULE_LOADER_WORKER`. In dev these are the fixed paths. + + API changes: + + - `emitWasmAssets` (`@inkandswitch/patchwork-bootloader/externals`) emits the wasm under hashed names and returns each one's reference id by file name. + - `@inkandswitch/patchwork/vite` has a `hashed()` plugin, included in `patchwork()`, which swaps in the hashed names. `hashedFiles(config)` gives a build's table of them, also exported from `@inkandswitch/patchwork/vite/hashed`. + - `workers` entries say whether a worker is `hashed`. The workers no longer import the packages from `/packages/…` URLs: they link to the build's chunks like the page does. + - `buildHeaders(options, hashed?)` takes that table for the wasm preloads. + - The new `buildRedirects(hashed?)` writes the redirects. `REVALIDATED_PATHS` and `REVALIDATED_CACHE_CONTROL` name the stale-while-revalidate files and their header. + + The e2e offline test waits for the wasm by the URLs the page preloads. + ## 0.2.3 ### Patch Changes diff --git a/packages/e2e/package.json b/packages/e2e/package.json index bda1f872b..689d447b4 100644 --- a/packages/e2e/package.json +++ b/packages/e2e/package.json @@ -5,7 +5,7 @@ "url": "git+https://github.com/inkandswitch/patchwork-system.git", "directory": "packages/e2e" }, - "version": "0.2.3", + "version": "0.2.4", "type": "module", "description": "Browser + Service-Worker end-to-end sync tests (Playwright) for a Patchwork site.", "bin": {