Skip to content

Paths in the sidebar: open at a path, package status, /packages as a folder - #94

Open
chee wants to merge 29 commits into
mainfrom
paths
Open

chee wants to merge 29 commits into
mainfrom
paths

Conversation

@chee

@chee chee commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

The base side of paths (inkandswitch/patchwork-system#467): opening documents at a path, package status in the sidebar, and /packages as a folder.

sideboard

  • Rows in the account's root folder open with their live path, <acctHash>/Projects/foo, so the router can set &path=. The path grammar (idhash, livePath) comes from the host's patchwork-filesystem at runtime, an importmap external, so it can't drift from the router's. A host without it gets no paths.
  • A row whose document is a package shows a dot: grey while loading, green when loaded, red when it failed. Its tooltip lists the plugins or the error. The status is read late from patchwork.packages (the ModuleWatcher's packageStatus and "status" event).
  • The shared item menu opens only in the panel that holds the row, so two list widgets don't both show it.
  • Docs that aren't folders but have children open up like folders: a directory doc (a pushwork package) shows its files, and a branches doc its branches. The children come from the host's patchwork-filesystem path providers, the same ones a path walks, so each child opens at a path the router resolves. They're read-only: they open and drag out, but have no Rename or Remove and take no drops.

providers

  • patchwork:selected-view carries path, taken from open-document events and from &path= in the hash. The hash value is read raw, because its segments are encoded.

threepane

  • The main <patchwork-view> gets path. Changing it doesn't remount the tool.
  • The account bar's Packages button goes once the account binds /packages, because packages then show in the root folder as a packages folder.

On a system without paths

Every new input is optional, and without it the tools behave as they do today:

  • no namespace: the Packages button stays;
  • no packageStatus: no dots;
  • no idhash/livePath in patchwork-filesystem: rows open without a path;
  • no providerFor: directory and branches docs stay plain rows;
  • the released router ignores path on open-document;
  • an unset path attribute sets nothing.

Nothing writes to documents. To try it against the released site, point a shell at this PR's deploy preview with localStorage.systemPackageListURL = "https://deploy-preview-<N>--<site>.netlify.app/modules.json".

Testing

New vitest tests:

  • sideboard: live-path, package-status, packages, account-bar, container, and container-item (renders a directory row, expands it, and checks a child opens at its path and is read-only);
  • providers: SelectedDocProvider;
  • threepane: MainDocumentView, packagesLink.

All three tools' suites pass, they build, and lint-standalone passes. Nothing has been run in a browser yet. Opening a nested row with its path is covered by the helper tests, not by a rendered tree. The isolated (iframe) document area doesn't receive path yet.

This branch also carries two commits that aren't part of the paths work: 078a092 ("dont let one broken lugin break the whole menu") and 6d5cbad ("fix lingering popper").

🤖 Generated with Claude Code

chee and others added 4 commits October 28, 2026 16:20
Each folder bound at /packages in the account's namespace gets a Packages
section under the root folder's list, using the same document list, so
installing a package is adding a link there, and renaming or removing it
is what the sidebar already does. The account bar's Packages link stays
only for accounts without /packages. A panel now shows the shared item
menu only for rows inside it, so a package row's menu acts on its own
folder.

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

- sideboard: rows in the account's root folder open with their live path
  (<acctHash>/Projects/foo), and a row whose document is a package shows a
  dot for whether it loaded, read from patchwork's ModuleWatcher. The
  separate Packages section is gone: /packages now shows in the root
  folder as a packages folder, so it's part of the one tree.
- providers: the selected view carries path, from open-document events and
  from &path= in the hash, read as encoded.
- threepane: the main view gets <patchwork-view path>, and the account
  bar's Packages button goes once /packages is bound.

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

patchcrow commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

✅ Patchwork glance ready

https://patchwork-preview-94--patchwork-base.netlify.app

Patchwork from inkandswitch/patchwork.inkandswitch.com, packages from this PR at 0c3cad6.

build-info.json

chee and others added 25 commits October 28, 2026 17:42
idhash and livePath come from patchwork-filesystem at runtime (an importmap
external), so a row's path can't drift from the router's. A host without
them gets no paths, and rows open as they did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A directory doc (a pushwork package) shows its files, and a branches doc its
branches, through the host's patchwork-filesystem path providers, the same
ones a path walks, so each child opens at a path the router resolves. The
children aren't entries in a folder, so they're read-only: they open and
drag out, but the menu has no Rename or Remove and they take no drops. A
host without providers shows these docs as plain rows, as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…p as far as it offers

Rows of a type a patchwork:path-provider plugin supports open up like
directory and branches docs. Their children include sub-documents, opened at
their sub-handle's url. Rename, Remove and dropping a document among them
are offered when the provider has rename, remove or add. Everything is
feature-detected from the host's patchwork-plugins and automerge-repo, so an
older host shows these docs as plain rows.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… put; providers follow plugin reloads

- Copy > Patchwork url on a sub-document row links to automerge:<id>/<path>,
  not the whole document.
- A drop on a container's children that doesn't take it no longer falls
  through to the provider container around it.
- A container re-asks for its provider when path-provider plugins change,
  so a reloaded plugin's code is used.
- A sub-document whose url can't be had is hidden, rather than an errored
  resource throwing during render.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tldraw5 registers a patchwork:path-provider for tldraw5 docs. It lists the
docs the canvas links (as a folder would, so paths through its script tree
resolve the same whichever provider walks it), then each shape as a
sub-document at store/<shape id>, named "<text or kind> (<id tail>)", with
"/" replaced. The module imports nothing from tldraw.

The tool, handed a shape's sub-document url, shows the whole canvas and
selects and zooms to that shape; a shape that's gone just opens the canvas.

sideboard opens a sub-document with no type of its own with its
container's type, so a shape opens in the canvas's tool.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ings, and on hosts that don't load it

The sidebar's packages folder stands in for the Packages tool only when the
host loads /packages and every module-settings entry is one migration moved
there (movedToPackages). Pinned and http entries, and anything an older host
installs later, still need the tool. Also sets patchwork-view's path through a
ref, since its JSX typings predate the attribute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
containerChildren builds new objects on every change, and <For> keys by
identity, so every child row remounted (and collapsed) on any edit, such as
pushwork's sync bumps. Unchanged children now keep their object.

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

In the account's root folder, the folder bound at /packages shows last,
set apart by a thin line. It can't be dragged, picked by the marquee, or
dropped beside; a drop on it goes inside (installing a package). It keeps
its real place in the folder doc, so rename, remove and drops elsewhere
work as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Renaming it would split its name in the sidebar from /packages, and
removing it would drop the root's link to it with no way to get it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a host that loads /packages, installing an automerge package links it
into your packages folder (the first /packages binding), named from its
package.json, else its title, else its id, and unique in the folder.
Uninstalling removes the link and any module-settings entry for it. Pinned
and http modules, and every install on an older host, still go into module
settings. A package linked from a folder you share shows where it's from,
with no delete: removing it there would uninstall it for everyone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Plugins a host loaded from /packages are registered under the package's
  pinned /@/ url; the tool now asks the host's ModuleWatcher (packageFor)
  which package that is, so they show as installed and can be removed.
  Before, every such package read as core.
- A package a folder you share also links can't be uninstalled here: the
  host would keep loading it from that folder.
- Installs go to /packages only from the account's own module-settings doc;
  another package list is edited as before.
- The folders are read through the host's own repo, not the tool element's,
  which a checked-out draft remaps to clones.
- A folder that couldn't be found is asked for again; an install that can't
  reach your folder says so rather than hanging.
- A moved bare entry left in module settings doesn't block installing that
  package pinned; a /packages binding that isn't a folder isn't written into;
  a second link with one name is skipped, as the host does; whether the host
  loads /packages is read from its package watcher.

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

- Providers were handed solid-automerge's live view, one object forever, so
  a provider that keeps what it lists per doc (tldraw's) never listed new
  shapes. They now get the doc itself, a new object on every change.
- A part of a document reads back from the hash decoded (shape:abc) where
  its row has it as automerge-repo writes it (shape%3Aabc). Selection now
  compares one key for both, so the part's row shows selected.
- A selected part opens the container it's in, and the folders above it,
  so its row is on screen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A patchwork:select-parts event ({ url, parts }) from a tool adds those
parts of the open document to the selection, without opening anything, so
the sidebar can show them selected. Parts of a document that isn't open are
ignored, and opening another document forgets them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Whenever the canvas's selection changes, the tool sends
patchwork:select-parts with each selected shape's part url, so the
sidebar shows those shapes selected (opening the canvas's row to show
them). It sends none on the way out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The open document's row keeps the full selection colour; a shape the canvas
says is selected gets the same colour, lighter, so the two read apart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e tool's parts follow it

Opening a shape from the sidebar made the shape the selection, and the
canvas's own selection was added beside it, so both read as open. Now the
document comes first and the part after it, and once the tool says what it
has selected, that replaces the part that was opened.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
create-new-menu: kept paths' listedDatatypes() (already sorted) over main's
in-menu sort, and dropped main's local label in favour of the one
solid-plugins exports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The sidebar's root list was the account's rootFolderUrl, seeded into the
threepane config once. It now shows what's bound at / (falling back to
rootFolderUrl, as accountBindings does), read live: one list per folder
bound there. Live paths and the pinned packages row go with the first,
since a later member's names can be shadowed by it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A list stays mounted when / is rebound, so its live paths were decided
once. They're now asked again when the account changes whether the list
is first at /. rootFolders also reads bindings as accountBindings does:
an ImmutableString url counts, and a / that isn't a list is ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

2 participants