~ - #442
Merged
Merged
~#442
Conversation
createRepo and the automerge protocol handler worker open IndexedDB on their own thread (IndexedDBStorageAdapter) instead of through a dedicated worker per tab. The origin-wide signer is unchanged. sites/bench grows from three modes to six — patchwork, pertab, pertab-bc, pertab-mesh (patchwork's mesh with per-tab signers), tab-worker (the node in a dedicated Worker per tab) and shared-worker (one SharedWorker node) — plus a storage-adapter comparison, an offline switch that really cuts the socket, a probed server peer id so "the server holds our heads" means the server, and retries that re-sync rather than re-read a settled query. Measured: the IndexedDB worker adapter bought no main-thread time and cost ~30 MB per three tabs; a bare relay node stores what a tab pushes but never carries it to the server on its own; storageless tabs lose edits made just before closing; and one Subduction identity shared by three tabs can strand a sibling on the mesh or leave the server unable to confirm a tab's heads, depending on timing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…preview Measured against a fresh server, the origin-wide signer strands the third tab and loses server confirmation every run, in patchwork, in the bare mesh with the shared signer, and in a keyhive build; the earlier "mostly converges" came from a vite preview left behind by another checkout that Playwright reused and that served a different build. reuseExistingServer is off. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A spec that measures each tab's footprint at six stops between about:blank and the shipped shape, at one and ten tabs, so the per-tab cost can be split into Chrome's floor, the bundle, the wasm, the Repo and the doc. ?stop=js and ?stop=wasm halt the page before the wasm and before the Repo. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… drained Repo.removeFromCache did nothing under Subduction: the source's detach was empty, so the entry kept the handle and its wasm document alive, and a later find re-used the stale entry and never synced again. The fork patch gives the source a real detach (persist unsaved commits, one sync round if no peer has them, drop the entry and listener, unsubscribe the topic) and makes removeFromCache await it. The worker counts handoffs in flight and evicts every loaded document five seconds after the last one finishes; the headless redirect waits for the folder to catch up with its peers first. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… IndexedDB Every Repo on the origin keeps the same Subduction signer and PeerId and its own socket to the sync server. The fork's storage bridge now announces each durable write on a BroadcastChannel, and the other nodes on the origin ingest those records from the shared database into their own Subduction instance via storeBuiltBatch, without re-writing them and without opening a server round. Ingestion is scoped to sedimentrees the node has attached. A find resolves from local storage as soon as the doc is there, before the first server round. The siblings Subduction mesh is removed. At 40 tabs against the sync server: every tab sees every sibling edit (was: only the creator's tab), the server confirms every edit (was: none), find median under 60 ms, no sibling-induced server rounds. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The patches plugin reads this repo's pnpm patch of @automerge/automerge-repo, shipped in the package as dist/patches, and applies its dist hunks to every copy vite bundles: the page's import-map chunks, the dev server's pre-bundled deps, and module workers built through worker.plugins. It replaces the two hand-written edits, so a site installing from npm gets the same automerge-repo as this workspace, storage bus included. Strict matching: a hunk that does not fit, or a version other than the one the patch names, fails the build. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Contributor
✅ Patchwork glance readyhttps://patchwork-preview-442.patchwork.inkandswitch.comA peek at |
The keyhive repo adapter's entry module carries the keyhive wasm as a 3 MB base64 string, and it was statically imported into every tab and the protocol-handler worker whether or not the site enabled keyhive. It is now imported inside the keyhive branches only. patchwork-plugins and patchwork-elements decide whether a document is a keyhive document from its id bytes instead of constructing a keyhive DocumentId, which is an ed25519 point decode and accepted about half of legacy padded ids. Measured 7 to 10 MB less per tab and 8 MB less in the protocol-handler worker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e shared database A tab that persists commits it authored posts the document's new heads on a BroadcastChannel. A sibling with the document open reloads it from the shared IndexedDB into its handle; one that does not, or that already knows those heads, does nothing. The siblings mesh goes with it — under one peer id the Subduction core never pushes a commit back to the sending peer's other connections, so those links only added rounds. The channel also carries what the sync server holds. Every tab talks to that server as the same identity, so one tab's onRemoteHeads observation is a fact for all of them; each keeps the newest per peer rather than the last to arrive, and only a real observation is announced, never a relay of a relay. A reload updates the handle, not the Subduction node's resident tree, so a commit that reached a tab only over the channel is one that tab cannot push. A containment backstop watches for that: when a handle holds heads no peer has been seen holding and a settling delay passes without a sibling reporting the server has them, the tab writes those commits a second time — which is what puts them in its tree — and opens a round that can carry them. At most one such write per commit, and an offline tab still does it, so the reconnect round finds the tree already correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bench page is a playwright harness with no UI, so there was no way to feel a topology, only to time it. ?ui loads a small page over the same window.bench: an append-only list (array appends merge, so two tabs writing while cut off both survive), a button that cuts this tab's socket through the offline spec's proxy, and a readout of the document's heads against the server's, sync rounds, storage writes and server bytes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bare @automerge/automerge, @automerge/automerge-repo, @automerge/automerge-subduction and @keyhive/keyhive specifiers resolve to their /slim builds everywhere: the vite plugin's bundle, the importmap a tool sees at runtime, and the dev server's worker bundles. The fullfat entries embed and instantiate their own copy of the wasm on import, so a single value import of a bare name — there were four in our own packages — cost every tab a second automerge instance and a second, byte-identical automerge.wasm. initWasm in the host and the protocol-handler worker hand wasm-bindgen a Request rather than buffering the bytes, so both automerge and subduction go through WebAssembly.instantiateStreaming: no 5 MB transient copy, and the compiled module is eligible for Chrome's code cache. pnpm lint fails on any import of a bare name in the table, type-only ones included, so the fullfat entries stay out of every bundle. CI runs it. The service worker also stops re-caching a passthrough response whose etag matches the copy it holds. Every tab boot used to clone the whole bundle's responses and write them back, holding a second copy of each body until the write landed; several tabs at once peaked at a few hundred MB. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The test nudged ten times over 1.5s and then asserted exactly three rounds had opened. Under a loaded machine the third had not always landed, so it failed about one full-suite run in three while passing alone every time. It now nudges until the cap is reached, asserts the count there, then keeps nudging to show it stays put. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…caping or encoding' Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The new playground has injection vulnerabilities and broken controls, while several benchmark and catch-up paths can report incorrect results or incur avoidable delays.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 2
Open (9)
Escape query parameters before inserting into innerHTML · New Escape collaborative data before rendering as HTML · New Recheck heads after installing event listeners · New Replace existing name parameter instead of appending · New Do not expose offline control for unsupported tab modes · New Treat failed document fetches as probe failures · New Update stale release note about pnpm patch packaging · New Remove contradictory pnpm patch caveat · New Update stale mode count in documentation · New
What changed in this PR
Optimizes Patchwork’s memory and synchronization behavior, packages Automerge patches for consumers, and expands benchmark coverage.
Changes:
- Replaces sibling meshes with IndexedDB-backed heads announcements and adds document eviction.
- Standardizes slim Automerge imports and lazy-loads Keyhive/Wasm resources.
- Adds topology, scale, memory, storage, offline, and eviction benchmarks.
| File | Description |
|---|---|
sites/bench/tests/sync.spec.ts |
Makes sync benchmarks resilient and adds server verification. |
sites/bench/tests/storage.spec.ts |
Measures storage recovery after closing writers. |
sites/bench/tests/storage-adapter.spec.ts |
Refines storage timing and stall metrics. |
sites/bench/tests/scale.spec.ts |
Adds multi-tab scale benchmarks. |
sites/bench/tests/offline.spec.ts |
Expands offline synchronization measurements. |
sites/bench/tests/memory.spec.ts |
Adds renderer memory profiling. |
sites/bench/tests/global-teardown.ts |
Renders missing measurements safely. |
sites/bench/tests/global-setup.ts |
Supports appending benchmark results. |
sites/bench/tests/evict.spec.ts |
Adds document-eviction benchmarks. |
sites/bench/tests/debug.spec.ts |
Removes obsolete debug tests. |
sites/bench/tests/churn.spec.ts |
Updates tab-churn measurement setup. |
sites/bench/tests/boot.spec.ts |
Waits for worker linkage metrics. |
sites/bench/tests/bench.ts |
Adds benchmark modes, network controls, and metrics. |
sites/bench/src/protocol.ts |
Extends worker protocol metadata and logging. |
sites/bench/src/playground.ts |
Adds an interactive synchronization playground. |
sites/bench/src/node.ts |
Adds worker-node relaying and peer tracking. |
sites/bench/src/main.ts |
Implements new topologies and instrumentation. |
sites/bench/README.md |
Documents benchmark modes and behavior. |
sites/bench/bench.config.ts |
Prevents reuse of stale preview servers. |
scripts/lint-slim-imports.mts |
Adds enforcement for slim imports. |
pnpm-lock.yaml |
Updates the Automerge patch hash. |
packages/providers/frameworks/solid/src/index.ts |
Uses slim Automerge types. |
packages/providers/frameworks/react/src/index.ts |
Uses slim Automerge types. |
packages/providers/core/src/types.ts |
Uses slim Automerge types. |
packages/providers/core/src/repo-provider.ts |
Uses slim Automerge types. |
packages/providers/core/src/overlay-repo.ts |
Uses slim Automerge types. |
packages/providers/core/src/overlay-handle.ts |
Uses slim Automerge types. |
packages/providers/core/src/index.ts |
Uses slim Automerge types. |
packages/edge-handles/src/edge-handle.ts |
Uses slim Automerge types. |
package.json |
Adds the slim-import lint command. |
core/plugins/src/tools.ts |
Uses slim Automerge types. |
core/plugins/src/keyhive.ts |
Detects Keyhive documents without runtime Keyhive imports. |
core/plugins/src/datatypes.ts |
Uses slim Automerge imports. |
core/patchwork/src/vite/service-worker-plugin.ts |
Suppresses worker module preloads. |
core/patchwork/src/vite/patches-plugin.ts |
Applies packaged pnpm patches during bundling. |
core/patchwork/src/vite/importmap-plugin.ts |
Aliases full Automerge imports to slim chunks. |
core/patchwork/src/vite/dev-plugin.ts |
Aligns development worker imports with slim chunks. |
core/patchwork/src/vite/config-plugin.ts |
Applies patches to worker builds. |
core/patchwork/src/repo.ts |
Uses direct IndexedDB, heads channels, and lazy Keyhive. |
core/patchwork/src/index.ts |
Exposes slim Automerge globals. |
core/patchwork/src/head.ts |
Maps runtime imports to slim chunks. |
core/patchwork/package.json |
Packages the Automerge patch. |
core/filesystem/test/heads-channel.test.ts |
Adds extensive heads-channel tests. |
core/filesystem/src/urls.ts |
Uses slim Automerge types. |
core/filesystem/src/types.ts |
Uses slim Automerge types. |
core/filesystem/src/find-handle.ts |
Uses slim Automerge types. |
core/elements/src/patchwork-view.ts |
Uses lightweight Keyhive document detection. |
core/elements/src/legacy-impl.ts |
Fixes document-ID handling and removes runtime Keyhive imports. |
core/elements/src/events.ts |
Uses slim Automerge types. |
core/bootloader/src/siblings.ts |
Removes the sibling-mesh adapter. |
core/bootloader/src/service-worker.ts |
Avoids recaching unchanged ETag responses. |
core/bootloader/src/externals.ts |
Exports slim alias metadata. |
core/bootloader/src/externals-list.ts |
Defines slim package aliases. |
core/bootloader/src/automerge-protocol-handler-worker.ts |
Adds catch-up waits and idle eviction. |
core/bootloader/package.json |
Removes the obsolete siblings export. |
.github/workflows/ci.yml |
Runs slim-import linting in CI. |
.changeset/skip-recaching-unchanged.md |
Documents service-worker cache optimization. |
.changeset/one-automerge-wasm.md |
Documents slim imports and shared Wasm usage. |
.changeset/lazy-keyhive.md |
Documents lazy Keyhive loading. |
.changeset/in-thread-indexeddb.md |
Documents direct IndexedDB usage. |
.changeset/heads-announcements.md |
Documents heads-channel synchronization. |
.changeset/evict-after-handoff.md |
Documents protocol-worker eviction. |
.changeset/apply-pnpm-patch-in-vite.md |
Documents consumer-side patch application. |
Files not reviewed (1)
- pnpm-lock.yaml: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+105
to
+108
| $("cut").addEventListener("click", () => { | ||
| offline = !offline; | ||
| bench.setOffline(offline); | ||
| $("cut").textContent = offline ? "go online" : "go offline"; |
Add immediate check for aborted signal in event listener Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
chee
marked this pull request as ready for review
September 23, 2026 16:51
Update eviction process for documents in Repo to improve cache management and syncing behavior. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Clarify the functionality of `patchwork` and its components, including the service worker and automerge worker. Explain the behavior of the tab's Repo in worker modes and its interaction with peers. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



No description provided.