Skip to content

(paths draft) - #467

Draft
chee wants to merge 81 commits into
mainfrom
paths-design
Draft

chee wants to merge 81 commits into
mainfrom
paths-design

Conversation

@chee

@chee chee commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

(this is a big draft PR to just work out the feeling, before trying to figure out what the primitives are that this could be built on top of)

chee and others added 8 commits October 28, 2026 03:35
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Literal doc-shaped segments inside a multi-segment directory key resolve;
a pinned root followed by names is reported non-canonical and resolves
live; a null folder entry is skipped; / falls back to rootFolderUrl when
every binding is malformed; FolderStrategy stays as a deprecated alias.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Benchmarks: Patchwork with this PR against main

a1b1853 against main at 7d4520f, Patchwork as it ships (patchwork mode), in chromium, firefox and webkit, each on its own runner with main and the PR measured one after the other (run). Lower is better throughout. A row is in the table when every run of the PR came out on the same side of every run of main, with the medians more than 20% apart; every benchmark is below it.

This run fails: the PR fails a check main passes.

  • firefox: …the server confirmed every edit within 5s failed in run 1 of the PR's 3; main passed it in all 3 runs. Run 1: 5 of 10 edits unconfirmed; the first, edit 6: page.evaluate: server never confirmed automerge:VHz3gLdrV1XqrcALc2ckwp6AUSo: it holds heads 17hTsUD7…,2E73NCrc…, the tab 17hTsUD7…
browser benchmark main PR change
chromium boot → repo, first tab (10 tabs) 261 ms (259–262) 316 ms (314–319) +21% worse
chromium …the server confirmed every edit within 5s 2/3 ok ok fixed
firefox …server confirmed every edit (40 tabs) 2/3 ok ok fixed
firefox …the server confirmed every edit within 5s ok 2/3 ok fails 1 of 3
every benchmark: the median of 3 runs of main and 3 runs of the PR, their range in brackets
browser benchmark main PR change
chromium boot → repo, first tab (1 tabs) 271 ms (268–271) 317 ms (301–318) +17%
chromium boot → server connected, first tab (1 tabs) 570 ms (568–577) 624 ms (598–638) +9%
chromium renderer memory, all tabs + workers (1 tabs) 350 MB (341–360) 362 MB (358–363) +3%
chromium renderer processes (1 tabs) 3 3
chromium boot → repo, first tab (3 tabs) 279 ms (260–280) 320 ms (315–346) +15%
chromium boot → repo, last tab (3 tabs) 206 ms (186–207) 220 ms (212–222) +7%
chromium boot → server connected, first tab (3 tabs) 588 ms (574–616) 658 ms (638–663) +12%
chromium renderer memory, all tabs + workers (3 tabs) 689 MB (688–702) 694 MB (680–706) +1%
chromium renderer processes (3 tabs) 5 5
chromium boot → repo, first tab (10 tabs) 261 ms (259–262) 316 ms (314–319) +21% worse
chromium boot → repo, last tab (10 tabs) 203 ms (199–233) 229 ms (221–243) +13%
chromium boot → server connected, first tab (10 tabs) 584 ms (567–591) 632 ms (621–640) +8%
chromium renderer memory, all tabs + workers (10 tabs) 1827 MB (1821–1831) 1876 MB (1799–1907) +3%
chromium renderer processes (10 tabs) 12 12
chromium sync still works after the first tab closes ok ok
chromium edit → seen + confirmed, after first tab closed 194 ms (192–195) 196 ms (193–204) +1%
chromium server link seen down while offline ok ok
chromium …and the server did not see the offline edits ok ok
chromium tabs converged while offline, over a local channel ok ok
chromium offline edits in two tabs both survive reconnect ok ok
chromium reconnect → tabs converged (only where they hadn't already) – –
chromium …and a fresh tab sees both ok ok
chromium boot → doc open, tab 1 754 ms (736–769) 776 ms (768–812) +3%
chromium boot → doc open, tab 2 357 ms (355–367) 378 ms (378–391) +6%
chromium boot → doc open, tab 10 317 ms (315–329) 335 ms (326–336) +6%
chromium open 40 tabs, wall clock 9990 ms (9982–10112) 10627 ms (10540–10640) +6%
chromium boot → repo, last of 40 tabs 73 ms (72–80) 74 ms (72–76) +2%
chromium boot → linked, last of 40 tabs – –
chromium boot → server connected, last of 40 tabs 502 ms (495–512) 511 ms (510–525) +2%
chromium renderer memory, 40 tabs 7075 MB (7044–7130) 7073 MB (7035–7114) −0%
chromium renderer processes, 40 tabs 42 42
chromium 39 tabs find the doc: median 864 ms (850–864) 864 ms (841–889) −0%
chromium 39 tabs find the doc: slowest 968 ms (967–1039) 980 ms (959–1015) +1%
chromium …all 39 found it ok ok
chromium …finds that needed a retry (of 39) 0 0
chromium edit → seen in 39 tabs: median tab 56 ms (52–57) 59 ms (59–60) +6%
chromium edit → seen in 39 tabs: slowest tab 117 ms (103–138) 141 ms (136–145) +20%
chromium …tabs that saw every edit (of 39) 39 39
chromium edit → server holds our heads (40 tabs) 215 ms (213–217) 211 ms (210–219) −1%
chromium …server confirmed every edit (40 tabs) ok ok
chromium …longest main-thread stall in tab 0 during the edits 51 ms (24–72) 28 ms (26–45) −45%
chromium sync rounds per sibling edit, non-editing tab 0 0
chromium server bytes received per edit per tab – –
chromium storage writes (saveBatch) per edit per tab – –
chromium worker errors (40 tabs) – –
chromium a fresh tab finds a closed tab's doc (no server) ok ok
chromium …time to find it, from storage 43 ms (39–46) 50 ms (39–63) +16%
chromium …and every edit made before the close is in it ok ok
chromium edits from tabs closed right after editing all survive ok ok
chromium …or at least 5s later ok ok
chromium find a doc another tab just created 58 ms (54–59) 53 ms (51–54) −9%
chromium …and every such find() succeeded within 5s ok ok
chromium …finds that settled unavailable first (of 2) 0 0
chromium edit → seen in 2 other tabs 7 ms (7–7) 7 ms (7–8) +3%
chromium …every edit reached every other tab within 5s ok ok
chromium edit → server holds our heads 150 ms (149–151) 151 ms (150–152) +0%
chromium …the server confirmed every edit within 5s 2/3 ok ok fixed
chromium …and a stranger gets the whole doc from the server ok ok
chromium renderer memory, 1 tab 351 MB (350–360) 357 MB (351–364) +2%
chromium renderer memory, 10 tabs 1824 MB (1821–1858) 1871 MB (1870–1894) +3%
chromium sha-256 of a 10KB hit – 0 ms
chromium sha-256 of a 100KB hit – 0 ms (0–0)
chromium sha-256 of a 1MB hit – 1 ms (1–1)
firefox boot → repo, first tab (1 tabs) 551 ms (541–563) 651 ms (628–662) +18%
firefox boot → server connected, first tab (1 tabs) 1437 ms (1420–1447) 1523 ms (1497–1536) +6%
firefox renderer memory, all tabs + workers (1 tabs) – –
firefox renderer processes (1 tabs) – –
firefox boot → repo, first tab (3 tabs) 554 ms (548–567) 637 ms (626–646) +15%
firefox boot → repo, last tab (3 tabs) 412 ms (406–417) 457 ms (456–460) +11%
firefox boot → server connected, first tab (3 tabs) 1460 ms (1458–1516) 1578 ms (1565–1581) +8%
firefox renderer memory, all tabs + workers (3 tabs) – –
firefox renderer processes (3 tabs) – –
firefox boot → repo, first tab (10 tabs) 581 ms (559–643) 757 ms (636–789) +30%
firefox boot → repo, last tab (10 tabs) 436 ms (417–436) 477 ms (464–486) +9%
firefox boot → server connected, first tab (10 tabs) 1694 ms (1483–1770) 1745 ms (1591–1791) +3%
firefox renderer memory, all tabs + workers (10 tabs) – –
firefox renderer processes (10 tabs) – –
firefox sync still works after the first tab closes ok ok
firefox edit → seen + confirmed, after first tab closed 362 ms (359–365) 370 ms (366–374) +2%
firefox server link seen down while offline ok ok
firefox …and the server did not see the offline edits ok ok
firefox tabs converged while offline, over a local channel ok ok
firefox offline edits in two tabs both survive reconnect ok ok
firefox reconnect → tabs converged (only where they hadn't already) – –
firefox …and a fresh tab sees both ok ok
firefox boot → doc open, tab 1 1639 ms (1619–1681) 1706 ms (1677–1746) +4%
firefox boot → doc open, tab 2 540 ms (536–540) 594 ms (592–598) +10%
firefox boot → doc open, tab 10 508 ms (483–511) 536 ms (523–571) +5%
firefox open 40 tabs, wall clock 26009 ms (25864–26022) 27402 ms (27362–28939) +5%
firefox boot → repo, last of 40 tabs 228 ms (224–248) 205 ms (201–424) −10%
firefox boot → linked, last of 40 tabs – –
firefox boot → server connected, last of 40 tabs 941 ms (938–993) 969 ms (962–1204) +3%
firefox renderer memory, 40 tabs – –
firefox renderer processes, 40 tabs – –
firefox 39 tabs find the doc: median 394 ms (384–401) 395 ms (386–402) +0%
firefox 39 tabs find the doc: slowest 400 ms (398–409) 405 ms (400–408) +1%
firefox …all 39 found it ok ok
firefox …finds that needed a retry (of 39) 0 0
firefox edit → seen in 39 tabs: median tab 126 ms (125–130) 128 ms (127–134) +1%
firefox edit → seen in 39 tabs: slowest tab 217 ms (207–264) 219 ms (209–222) +1%
firefox …tabs that saw every edit (of 39) 39 39
firefox edit → server holds our heads (40 tabs) 455 ms (441–469) 458 ms (448–466) +1%
firefox …server confirmed every edit (40 tabs) 2/3 ok ok fixed
firefox …longest main-thread stall in tab 0 during the edits 134 ms (133–161) 144 ms (134–146) +8%
firefox sync rounds per sibling edit, non-editing tab 0 0
firefox server bytes received per edit per tab – –
firefox storage writes (saveBatch) per edit per tab – –
firefox worker errors (40 tabs) – –
firefox a fresh tab finds a closed tab's doc (no server) ok ok
firefox …time to find it, from storage 31 ms (30–32) 34 ms (33–227) +10%
firefox …and every edit made before the close is in it ok ok
firefox edits from tabs closed right after editing all survive ok ok
firefox …or at least 5s later ok ok
firefox find a doc another tab just created 33 ms (32–33) 32 ms (31–33) −4%
firefox …and every such find() succeeded within 5s ok ok
firefox …finds that settled unavailable first (of 2) 0 0
firefox edit → seen in 2 other tabs 18 ms (18–18) 18 ms (17–18) −2%
firefox …every edit reached every other tab within 5s ok ok
firefox edit → server holds our heads 259 ms (258–259) 259 ms (258–261) +0%
firefox …the server confirmed every edit within 5s ok 2/3 ok fails 1 of 3
firefox …and a stranger gets the whole doc from the server ok ok
firefox sha-256 of a 10KB hit – 0 ms (0–0)
firefox sha-256 of a 100KB hit – 0 ms (0–0)
firefox sha-256 of a 1MB hit – 1 ms (1–1)
webkit boot → repo, first tab (1 tabs) 509 ms (489–536) 549 ms (549–562) +8%
webkit boot → server connected, first tab (1 tabs) 592 ms (558–610) 602 ms (598–627) +2%
webkit renderer memory, all tabs + workers (1 tabs) – –
webkit renderer processes (1 tabs) – –
webkit boot → repo, first tab (3 tabs) 525 ms (520–536) 564 ms (551–568) +7%
webkit boot → repo, last tab (3 tabs) 435 ms (424–443) 433 ms (431–439) −0%
webkit boot → server connected, first tab (3 tabs) 604 ms (595–617) 629 ms (619–632) +4%
webkit renderer memory, all tabs + workers (3 tabs) – –
webkit renderer processes (3 tabs) – –
webkit boot → repo, first tab (10 tabs) 515 ms (513–520) 551 ms (537–562) +7%
webkit boot → repo, last tab (10 tabs) 440 ms (422–463) 456 ms (439–464) +4%
webkit boot → server connected, first tab (10 tabs) 602 ms (582–623) 609 ms (599–633) +1%
webkit renderer memory, all tabs + workers (10 tabs) – –
webkit renderer processes (10 tabs) – –
webkit sync still works after the first tab closes ok ok
webkit edit → seen + confirmed, after first tab closed 66 ms (60–66) 69 ms (67–73) +5%
webkit server link seen down while offline ok ok
webkit …and the server did not see the offline edits ok ok
webkit tabs converged while offline, over a local channel ok ok
webkit offline edits in two tabs both survive reconnect ok ok
webkit reconnect → tabs converged (only where they hadn't already) – –
webkit …and a fresh tab sees both ok ok
webkit boot → doc open, tab 1 706 ms (696–717) 734 ms (720–776) +4%
webkit boot → doc open, tab 2 541 ms (518–553) 553 ms (545–575) +2%
webkit boot → doc open, tab 10 575 ms (539–613) 615 ms (601–647) +7%
webkit open 40 tabs, wall clock 34448 ms (34124–34816) 35299 ms (34763–35446) +2%
webkit boot → repo, last of 40 tabs 228 ms (218–243) 222 ms (221–230) −3%
webkit boot → linked, last of 40 tabs – –
webkit boot → server connected, last of 40 tabs 533 ms (502–544) 567 ms (549–576) +6%
webkit renderer memory, 40 tabs – –
webkit renderer processes, 40 tabs – –
webkit 39 tabs find the doc: median 596 ms (568–651) 558 ms (542–575) −6%
webkit 39 tabs find the doc: slowest 677 ms (660–722) 689 ms (657–1044) +2%
webkit …all 39 found it ok ok
webkit …finds that needed a retry (of 39) 0 0
webkit edit → seen in 39 tabs: median tab 101 ms (97–101) 104 ms (100–106) +3%
webkit edit → seen in 39 tabs: slowest tab 248 ms (173–360) 283 ms (269–322) +14%
webkit …tabs that saw every edit (of 39) 39 39
webkit edit → server holds our heads (40 tabs) 341 ms (331–351) 329 ms (313–329) −4%
webkit …server confirmed every edit (40 tabs) ok ok
webkit …longest main-thread stall in tab 0 during the edits 75 ms (72–83) 72 ms (42–77) −4%
webkit sync rounds per sibling edit, non-editing tab 0 0
webkit server bytes received per edit per tab – –
webkit storage writes (saveBatch) per edit per tab – –
webkit worker errors (40 tabs) – –
webkit a fresh tab finds a closed tab's doc (no server) ok ok
webkit …time to find it, from storage 94 ms (93–103) 103 ms (85–110) +10%
webkit …and every edit made before the close is in it ok ok
webkit edits from tabs closed right after editing all survive ok ok
webkit …or at least 5s later ok ok
webkit find a doc another tab just created 57 ms (50–60) 45 ms (41–60) −21%
webkit …and every such find() succeeded within 5s ok ok
webkit …finds that settled unavailable first (of 2) 0 0
webkit edit → seen in 2 other tabs 16 ms (14–17) 15 ms (14–16) −6%
webkit …every edit reached every other tab within 5s ok ok
webkit edit → server holds our heads 37 ms (32–43) 31 ms (31–46) −15%
webkit …the server confirmed every edit within 5s ok ok
webkit …and a stranger gets the whole doc from the server ok ok
webkit sha-256 of a 10KB hit – 0 ms
webkit sha-256 of a 100KB hit – 0 ms
webkit sha-256 of a 1MB hit – 1 ms

@github-actions

github-actions Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

✅ Patchwork glance ready

https://patchwork-preview-467.patchwork.inkandswitch.com

A peek at inkandswitch/patchwork.inkandswitch.com, built with the @inkandswitch/patchwork- packages as of this PR at a1b1853.

chee and others added 14 commits October 28, 2026 05:37
Records, per browser, whether the service worker can reach a module
SharedWorker client, and what it learns about the sender on each reply
path (controller postMessage, a transferred port, fetch clientId, and a
worker-initiated port handoff), alongside a dedicated worker loaded from
the same URL. Runs in CI; the results land in the job summary.

Also records in the spec that automerge verifies change hashes on load.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only a fetch() from the worker, checked by clientId → Client type and
url, works in Chromium, Firefox and WebKit. Index answers go that way;
requests can stay broadcast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ce worker

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ir account

The walk now records the last container it entered, and listFiles lists
under it, so a manifest request for a file covers its whole package.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A manifest answer settles only the request for the same path, so a page
  can't get the worker to answer a forged copy and switch the check off.
- The worker probes /@/.index before posting, so a fetch that skips the
  service worker never carries file names to the origin server.
- A tab declines paths under accounts it doesn't know yet, instead of
  answering 404 while it boots.
- Listing a package reads file docs without waiting for peers, several at
  once, on a budget of its own, and a partial manifest isn't kept.
- After a manifest times out, requests wait 250ms instead of 10s until the
  worker answers again.
- Immutable legacy responses are cached under the request only, so a
  navigation no longer replaces the offline app shell.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread core/bootloader/src/resolver.ts Fixed
chee and others added 5 commits October 28, 2026 12:33
A harness runs the real service worker module in node against a fake
global scope (self, clients, caches) with node's own BroadcastChannel and
locks, and dispatches fetch and lifecycle events at it. Tabs are fake
window clients running the real tab resolver.

Covers the legacy navigation fix (an immutable response is kept under its
own URL, not over the app shell's / and /index.html), the index channel
refusing a page and answering the worker's probe, and activate keeping
content stores. The first two fail against the service worker before
a2299b3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A container's pinned step carries ~<listHash>, the SHA-256 of a
canonical list of the files below it (bytes hash, type, heads read at).
The list is content like any other, so a pinned URL is checked against
itself: links no longer need heads to pin their files, the in-memory
index and the authenticated channel go, and a cold start needs no
document. No document ids in URLs or in the store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…list

Hashing and the keyhive check move into filesystem; plugins' isKeyhiveDoc
goes through it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
chee and others added 6 commits October 28, 2026 20:45
…oviders in walks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…esn't

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pinning, nested subs listed, sub links refused

- A lookup inside a document that consumes no name, or more names than
  there are, is no child, so a buggy provider can't spin the walk.
- Whether a document a path ends at is a container is the built-ins'
  call, and so is what listFiles counts as a file: a path pins and lists
  the same with or without a package's provider, and installing one
  doesn't change what an existing pinned URL means.
- listFiles lists what's inside the values a package's provider hands
  out, as far as a walk can reach.
- A link with path segments (a sub-document) isn't walked or listed as
  the whole document.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ough

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ecks, worker isolation test

- A name a provider spreads over several segments is written as the file
  list writes it, so each spelling pins to a tail its list covers.
- A provider whose list isn't an array has no children.
- The router drops a path= naming a different part of doc='s document.
- resolve.ts no longer carries a sub branch nothing can reach.
- A static test holds that the automerge worker never gets package providers.
- Spec sections 2 and 6 say where package providers run and where drops land.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chee
chee requested a balanced review from Copilot October 29, 2026 04:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Concurrent package migrations can overwrite /packages bindings and detach a folder, leaving its edits unreachable through /packages or its sidebar link pointing to a different folder.

9 open issues
1 file not reviewed
  • pnpm-lock.yaml: Generated file
What changed in this PR

This draft adds account-rooted paths to Patchwork, connecting document navigation, package discovery, and verified content delivery through the filesystem and bootloader.

Changes:

  • Adds live and pinned paths, namespace mounts, and extensible path providers.
  • Introduces /packages discovery, account migration, package status, and path-aware routing.
  • Adds hash-verified content caching, automated coverage, benchmarks, and a browser worker-messaging spike.
File Description
vitest.config.ts Includes bootloader and plugins tests.
sites/​bench/​tests/​content-hash.spec.ts Benchmarks content-hash verification costs.
sites/​bench/​README.md Documents the hashing benchmark.
pnpm-lock.yaml Records the filesystem’s base58 dependency.
packages/​e2e/​tests/​path-routing.spec.ts Tests path-only document navigation.
packages/​e2e/​tests/​content-store.spec.ts Tests rejection of forged cached content.
packages/​e2e/​spikes/​sw-sharedworker/​spike.spec.mjs Records browser worker-messaging results.
packages/​e2e/​spikes/​sw-sharedworker/​serve.mjs Serves the messaging spike.
packages/​e2e/​spikes/​sw-sharedworker/​public/​sw.js Probes worker communication and sender identity.
packages/​e2e/​spikes/​sw-sharedworker/​public/​shared.js Implements shared and dedicated worker probes.
packages/​e2e/​spikes/​sw-sharedworker/​public/​index.html Coordinates browser messaging probes.
packages/​e2e/​spikes/​sw-sharedworker/​playwright.config.mjs Configures cross-browser spike execution.
packages/​e2e/​spikes/​sw-sharedworker/​.gitignore Excludes generated spike results.
docs/​superpowers/​plans/​2026-10-28-paths-sidebar.md Plans sidebar path and package-status integration.
docs/​superpowers/​plans/​2026-10-28-paths-phase-4-packages.md Plans namespace-based package loading.
docs/​superpowers/​plans/​2026-10-28-paths-phase-3-routing.md Plans path-aware hash routing.
core/​plugins/​vitest.config.ts Configures plugin unit tests.
core/​plugins/​test/​path-providers.test.ts Tests provider selection and validation.
core/​plugins/​src/​registry/​types.ts Adds the path-provider registry type.
core/​plugins/​src/​path-providers.ts Loads datatype-specific path providers.
core/​plugins/​src/​keyhive.ts Reuses filesystem document-protection detection.
core/​plugins/​src/​index.ts Exports path-provider APIs.
core/​plugins/​src/​account.ts Adds namespace typing and deprecates module settings.
core/​plugins/​package.json Enables the plugin test command.
core/​patchwork/​test/​router.test.ts Tests path routing and provider-backed subdocuments.
core/​patchwork/​test/​migrate-packages.test.ts Tests package migration and account initialization.
core/​patchwork/​test/​hash-params.test.ts Tests preservation of encoded paths.
core/​patchwork/​src/​types.ts Adds paths to document-opening options.
core/​patchwork/​src/​router.ts Resolves, preserves, and checks navigation paths.
core/​patchwork/​src/​migrate-packages.ts Migrates legacy package settings into a namespace folder.
core/​patchwork/​src/​index.ts Wires account registration and package-folder loading.
core/​patchwork/​src/​hash-params.ts Centralizes path-aware hash parsing and serialization.
core/​patchwork/​src/​createAccount.ts Creates namespace mounts and the packages tree link.
core/​filesystem/​test/​walk.test.ts Tests namespace traversal and path pinning.
core/​filesystem/​test/​resolve.test.ts Covers default-branch resolution.
core/​filesystem/​test/​providers.test.ts Tests built-in path providers.
core/​filesystem/​test/​paths.test.ts Tests path grammar and identifier hashing.
core/​filesystem/​test/​packages-folder.test.ts Tests package discovery and stable lookup URLs.
core/​filesystem/​test/​package-providers.test.ts Tests custom providers and subdocuments.
core/​filesystem/​test/​namespace-fixture.ts Supplies shared namespace fixtures.
core/​filesystem/​test/​files.test.ts Tests file reading and listing consistency.
core/​filesystem/​test/​file-list.test.ts Tests hashed lists and nested package files.
core/​filesystem/​src/​types.ts Defines namespace bindings and account shape.
core/​filesystem/​src/​resolve.ts Uses providers for legacy resolution and exposes node reads.
core/​filesystem/​src/​providers.ts Defines provider contracts and built-in implementations.
core/​filesystem/​src/​paths.ts Implements path parsing, formatting, and identifier hashing.
core/​filesystem/​src/​packages-folder.ts Watches package unions and resolves pinned import URLs.
core/​filesystem/​src/​module-watcher.ts Adds folder sources, lookup retries, and package status.
core/​filesystem/​src/​keyhive.ts Centralizes protected-document detection.
core/​filesystem/​src/​index.ts Exports namespace, path, and content APIs.
core/​filesystem/​src/​file-list.ts Builds canonical, hashed file manifests.
core/​filesystem/​src/​content-hash.ts Provides shared byte conversion and SHA-256 hashing.
core/​filesystem/​package.json Adds base58 encoding support.
core/​elements/​src/​patchwork-view.ts Exposes paths without remounting tools.
core/​elements/​src/​index.ts Adds path attributes to element typings.
core/​elements/​src/​events.ts Carries paths in document-opening events.
core/​bootloader/​vitest.config.ts Configures bootloader tests.
core/​bootloader/​test/​setup.ts Initializes Wasm modules for tests.
core/​bootloader/​test/​service-worker.test.ts Tests path serving, integrity checks, and cache lifecycle.
core/​bootloader/​test/​service-worker-harness.ts Provides an in-memory service-worker harness.
core/​bootloader/​test/​content-store.test.ts Tests hashing and content-store operations.
core/​bootloader/​src/​types.ts Extends handoff messages for paths and account registration.
core/​bootloader/​src/​tab-resolver.ts Registers accounts and enables tab-side path providers.
core/​bootloader/​src/​setup.ts Replays account registrations after worker restarts.
core/​bootloader/​src/​service-worker.ts Serves verified paths and manages content caches.
core/​bootloader/​src/​content-store.ts Adds per-account, hash-keyed storage and clearing.
core/​bootloader/​src/​automerge-protocol-handler.ts Registers accounts for worker-side path resolution.
core/​bootloader/​package.json Adds the bootloader test command.
.github/​workflows/​spike-sw-sharedworker.yml Runs and archives cross-browser messaging probes.
.changeset/​paths-routing.md Documents path-routing behavior.
.changeset/​paths-packages.md Documents package mounts, migration, and status APIs.
.changeset/​paths-grammar-providers.md Documents path grammar and provider API changes.
.changeset/​paths-content-store.md Documents verified content serving and caching.
.changeset/​path-provider-plugins.md Documents plugin providers and subdocument traversal.
.changeset/​open-document-path.md Documents path-aware document-opening APIs.

Review effort: Balanced

Comment on lines +142 to +144
account.change((doc) => {
doc.namespace ??= {};
doc.namespace["/packages"] = [{ url: folder.url }];
Comment thread core/filesystem/src/file-list.ts Outdated
const throughPackages = first?.kind === "name" && first.name === "packages";
const isProtected = options.isProtected ?? isKeyhiveDocumentId;

const files: Record<string, FileEntry> = {};
Comment on lines +495 to +496
// A /packages package comes back as its pinned /@/ URL, imported over http.
if (!isValidAutomergeUrl(latest)) return latest;
Comment thread core/filesystem/src/packages-folder.ts Outdated
Comment on lines +46 to +48
const handles = (
await Promise.all(urls.map((url) => repo.find<unknown>(url).catch(() => undefined)))
).filter((handle): handle is DocHandle<unknown> => !!handle);
Comment on lines +411 to +412
moduleWatcher
.addPackagesFolder(
Comment thread core/patchwork/src/migrate-packages.ts Outdated
Comment on lines +125 to +130
const taken = new Map<string, number>();
const links = described.map(({ url, name, type }) => {
const count = (taken.get(name) ?? 0) + 1;
taken.set(name, count);
return { name: count === 1 ? name : `${name} (${count})`, type, url };
});
* A path that doesn't resolve stays: it may name something not synced yet.
*/
const checkPath = async (docUrl: AutomergeUrl, path: string) => {
const handle = await resolvePathParam(path);
Comment thread core/patchwork/src/router.ts Outdated
console.warn(`patchwork: path=${path} doesn't resolve here (yet)`);
return;
}
if (handle.documentId !== parseAutomergeUrl(docUrl).documentId) {
Comment thread core/patchwork/src/router.ts Outdated
if (prevDoc && !sameDoc) params.delete("draft");
// Where the doc was opened from survives a tool switch or heads change on
// the same doc, as `draft` does.
const keptPath = !path && sameDoc ? params.get("path") ?? undefined : path;
chee and others added 21 commits October 29, 2026 10:40
…ment; path-only hashes retry when providers register

- A doc=/path= check that resolves after the hash moved on leaves the new
  hash alone: dropPath now needs doc= unchanged too.
- Opening another part of the same document without a path drops the
  path; only the same part (a tool or heads change) keeps it.
- A path-only hash that doesn't resolve is walked again whenever path
  providers change, so a cold boot opens a path through a package's
  datatype once that package registers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… mid-migration wins

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…branch's code changes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ed docs read at their heads

- A doc named by a doc step is walked with package providers too: only a
  container is written as one, so a pinned prefix through a package's
  container rebuilds that container's list, not its parent's. A path that
  ends at a doc by name still decides container or file by the built-ins.
- A value held by a doc below the container (a nested directory in a deep
  package) is listed with that doc's heads, and read back at them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…so removing one there removes it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e's provider

The folder built-in matches anything with a docs array, so a tldraw canvas
(which keeps its scripts there) never reached its own provider. A provider
declared for such a doc's type now walks it; an untyped or folder-typed
folder is still the built-in's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…others

Members are found independently. The first listing waits up to 3s for all
of them, then lists what it has; a slow member is listed when it arrives,
and one that couldn't be found is asked for again 10s later.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…no cache

The content store is opened lazily: reads and deletes of a cache that
doesn't exist find nothing, and only a verified write creates it. Before,
every made-up account name in a /@/ link left a cache behind that nothing
ever deleted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t stacks

From CodeQL on #467.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ckage doc

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…lled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A sub-document url (a tldraw shape from the sidebar, say) whose value has
no @Patchwork type picked no tool, and patchwork-view said the document was
missing its metadata. The tool is now chosen from the document it's part
of, and still gets the sub-handle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The pinned /@/ URL a /packages lookup answers with went straight to
importPackageFromHttpUrl on the main thread. It now goes through the
importAutomergePackage hook like any package doc's version, so the
worker reads its descriptors and load() re-imports the same pinned URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A file in a package's nested folder is reached through folder docs linked
without heads. After a rename, walking back to it at their current heads
found nothing, so its pinned URL failed when the bytes weren't stored.
Lists now record those folders' heads as via; the service worker passes
them to the resolver, and readPath reads each folder at them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A fake tab runs on the service worker's event loop, so its accepted can
queue behind its own resolving. Under load that passed the 250ms accept
timeout, the tab counted as silent, and the request waited out a 35s
broadcast nothing answers: the 30s test timeouts. Four parallel runs of
the suite failed 12 of 12 times before; 12 of 12 pass after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants