mesh: restore folder preservation in catalog gossip + Pull#209
Merged
Conversation
## Two regressions from the daemon migration ### Sidebar peer view: flat instead of folder-tree The legacy `mesh-client.svelte.ts::refreshLocalCatalog` included `path` on every `CatalogEntry` it shipped, so receivers' Sidebar `buildRemoteTree` could reproduce the host's folder structure (see `src/ui/Sidebar.svelte:337`, which still reads `e.path` and builds nested `RemoteNode`s). `mesh-gossip.ts::snapshotLocalCatalog` shipped in PR #203 dropped the `path` field on the map: return conversations.map((c) => ({ guid: c.id, title: c.title, mode: c.mode, updated_at: c.updated_at, })) as CatalogEntry[]; // ← no path Every receiver saw every remote conversation at root. A peer's `Work/Projects/Q4 planning` showed up as a flat `Q4 planning` row alongside top-level conversations — visually indistinguishable from a root-level chat. Reported by the user as "didn't folders used to transfer over [in the sidebar]?". Fix: thread `c.path` through the snapshot's map. Empty (root) omitted from the wire payload via the spread-only-when-truthy trick, matching the legacy `|| undefined` shape. ### Pull: pulled conversations land at root `pullConversation` was calling `saveConversation(conversation)` with no `targetFolder` argument. The conversation JSON itself doesn't carry path (that's a filesystem fact, not conversation content), so the receiver had no way to know where on the source the conversation lived → every Pull flattened to root regardless of where the conversation was on the source. Push (`moveConversation`) was already correct: it looks up `c.path` from the local listing and ships it as `move_take.source_folder`, which the receiver's `handleMoveTake` forwards into `saveConversation(conversation, source_folder)`. The Pull path was the missing half. Fix: `pullConversation` looks the source folder up from the cached catalog of the source peer (now that catalog gossip carries path again — without the gossip fix above, the catalog entry's `path` would still be undefined). Passes it as the second arg to `saveConversation`, which calls `pathFor` → `mkdir({ recursive: true })` to create intermediate folders. MoveClient gains a read-only `peers` accessor (with the minimum shape needed for the lookup: `device_pubkey`, `peer_id`, `catalog[].guid`, `catalog[].path`). Falls back to root when the catalog hasn't caught up — same disposition as a legitimate root-hosted conversation. ## Validation - `pnpm run check`: 164 files, 0 errors, 0 warnings. - `pnpm run build`: clean. - Two devices: push `Foo/Bar/baz` from A to B → arrives at `Foo/Bar/baz` on B (already worked before this PR, sanity check). - Two devices: pull `Foo/Bar/baz` from A → arrives at `Foo/Bar/baz` locally (was landing at root before). - Two devices: A has `Work/Q4 planning` chat; B's sidebar Network section nests `Work` as a folder containing `Q4 planning` (was a flat row before). https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk
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.
Yes, folders used to transfer — two regressions
The user reported "didn't folders used to transfer over [in the sidebar]?". Confirmed: two related drops from the daemon migration.
1. Sidebar peer view: flat instead of folder-tree
The legacy
mesh-client.svelte.ts::refreshLocalCatalogshippedpathon everyCatalogEntry. Receivers'src/ui/Sidebar.svelte::buildRemoteTree(still in place, unchanged) readse.pathand groups peer conversations into nestedRemoteNodes — that's how the user'sWork/Projects/Q4 planningon the source rendered as a folder tree on every other device.mesh-gossip.ts::snapshotLocalCatalogshipped in PR #203 dropped the field on the map:Every receiver saw every remote conversation at root. A peer's
Work/Projects/Q4 planningshowed up as a flatQ4 planningrow alongside top-level conversations — visually indistinguishable from a root-level chat.Fix: thread
c.paththrough the snapshot's map. Empty (root) omitted from the wire payload via spread-only-when-truthy, matching the legacy|| undefinedshape.2. Pull lands conversations at root regardless of source
pullConversationwas callingsaveConversation(conversation)with notargetFolderarg. TheConversationJSON the source returns doesn't carry path (filesystem fact, not conversation content), so the receiver had no way to know where on the source the conversation lived → every Pull flattened to root.Push (
moveConversation) was already correct: it looks upc.pathfrom the local listing and ships it asmove_take.source_folder, whichhandleMoveTakeforwards intosaveConversation(conversation, source_folder). The Pull path was the missing half.Fix:
pullConversationlooks the source folder up from the cached catalog of the source peer (now that the catalog carriespathagain — both fixes need each other). Passes it as the second arg tosaveConversation, which callspathFor→mkdir({ recursive: true })to create intermediate folders. Falls back to root when the catalog hasn't caught up.MoveClientgains a read-onlypeersaccessor (with the minimum shape needed for the lookup:device_pubkey,peer_id,catalog[].guid,catalog[].path).Validation
pnpm run check: 164 files, 0 errors, 0 warnings.pnpm run build: clean.Work/Q4 planning; B's sidebar Network section nestsWorkas a folder containingQ4 planning(was a flat row before).Foo/Bar/bazfrom A to B → arrives atFoo/Bar/bazon B (already worked, sanity check).Foo/Bar/bazfrom A → arrives atFoo/Bar/bazlocally (was landing at root before).https://claude.ai/code/session_01RLu1LdTgtxEDdzhybzqFrk
Generated by Claude Code