From 43c0a212e6107262c8654d8c97be6ce22419964a Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 16:54:09 +0000 Subject: [PATCH 1/6] docs(devices): describe removing a working copy --- content/docs/(overview)/devices.mdx | 2 ++ content/docs/run/troubleshooting.mdx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index 8e949545..a9979bfb 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,6 +39,8 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while another process writes the copy, so the app closes its own copy first. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. + ## A write made offline Every write a device makes shows in its copy at once, and waits in the queue until the instance answers it. Sending the queue is a **drain**. The timeline follows one note through it. diff --git a/content/docs/run/troubleshooting.mdx b/content/docs/run/troubleshooting.mdx index 2ccbba9a..2d744ba4 100644 --- a/content/docs/run/troubleshooting.mdx +++ b/content/docs/run/troubleshooting.mdx @@ -281,6 +281,8 @@ The message says how many writes the file holds that the server has not taken: w - **None.** Every write the file holds reached the server. Run `marfa device hydrate` to build a new working copy. An app built on the core's Node binding can call `replaceRefused` instead, and one built on the [Swift SDK](/swift-sdk#replace-a-refused-copy) can call `WorkingCopy.replaceRefused(store:server:fileBudget:)`: it replaces the old file with an empty copy, so what only the old file held, such as pins, is lost. The files the copy downloaded stay. - **Some.** Open the file with the build that made it, run `marfa device drain` to send the writes, and use `marfa device discard` on any it refuses that you don't want. Then hydrate a new copy with the newer build. Keep the old file until you have done this, because deleting it loses those writes. +An app that lets a person discard the copy instead can remove it through the core's Node or Swift binding, passing the count the person agreed to lose. The core removes the file, its logs, its downloaded files and its lock only if the file still holds exactly that count, and otherwise refuses with `unsent_differs` and the file's own count. [Devices](/devices#the-working-copy) explains the removal. + **A folder replaces its own store.** Each folder keeps a working copy of its own, with its record of which file is which item, which files went missing, which writes and placements the server refused and which notes were purged. A folder command that writes, such as `marfa folders push`, `marfa folders pull`, `marfa folders scan` or `marfa folders watch`, handles a store from another build itself: - **None.** If the store also holds no answer the folder has yet to record, the command replaces it with a store of its own shape, says so on standard error, and carries on. The new store keeps the folder's record of its files, its first sync, the server it was filled from and `.marfa/folder.yaml`, and is filled again from the server. **The replacement changes no file and asks for no new first sync.** The command then sends each file you edited or deleted while the store was refused, takes away the file of a note put in the bin in that time, still takes away the file of a note purged before the replacement, and keeps holding a file the server refused before. If you edited a file whose note went to the bin in that time, the edit is sent as a new note. From 81e223f61e48b9adc5531af4124ee03409de60bd Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 17:20:33 +0000 Subject: [PATCH 2/6] docs(devices): say the app's own copy also blocks a removal --- content/docs/(overview)/devices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index a9979bfb..52f03a9c 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,7 +39,7 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. -**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while another process writes the copy, so the app closes its own copy first. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. ## A write made offline From 1c1a28f4fb5b58be7047fd5433e2d8c227d907b2 Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 17:32:27 +0000 Subject: [PATCH 3/6] docs(devices): say a reading helper keeps showing a removed copy --- content/docs/(overview)/devices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index 52f03a9c..9067bb1e 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,7 +39,7 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. -**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. ## A write made offline From fa48407ceab21781d39c0b85d2f351b32c39ba92 Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 17:49:21 +0000 Subject: [PATCH 4/6] docs(devices): say what a refused removal leaves --- content/docs/(overview)/devices.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index 9067bb1e..02f0955e 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,7 +39,7 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. -**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and changes nothing. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. ## A write made offline From 239f4fe7d0392da9918f7d6a705f6425f4607164 Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 18:54:09 +0000 Subject: [PATCH 5/6] docs(devices): name the Swift call that removes a copy --- content/docs/(overview)/devices.mdx | 2 +- content/docs/run/troubleshooting.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index 02f0955e..069fb3f0 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,7 +39,7 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. -**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app built on the core's Node or Swift binding passes the number of writes the server hasn't taken that it showed the person. The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app passes the number of writes the server hasn't taken that it showed the person, through the core's Node binding or the Swift SDK's [`WorkingCopy.remove(store:unsent:)`](/swift-sdk#remove-a-copy). The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. ## A write made offline diff --git a/content/docs/run/troubleshooting.mdx b/content/docs/run/troubleshooting.mdx index 2d744ba4..25015263 100644 --- a/content/docs/run/troubleshooting.mdx +++ b/content/docs/run/troubleshooting.mdx @@ -281,7 +281,7 @@ The message says how many writes the file holds that the server has not taken: w - **None.** Every write the file holds reached the server. Run `marfa device hydrate` to build a new working copy. An app built on the core's Node binding can call `replaceRefused` instead, and one built on the [Swift SDK](/swift-sdk#replace-a-refused-copy) can call `WorkingCopy.replaceRefused(store:server:fileBudget:)`: it replaces the old file with an empty copy, so what only the old file held, such as pins, is lost. The files the copy downloaded stay. - **Some.** Open the file with the build that made it, run `marfa device drain` to send the writes, and use `marfa device discard` on any it refuses that you don't want. Then hydrate a new copy with the newer build. Keep the old file until you have done this, because deleting it loses those writes. -An app that lets a person discard the copy instead can remove it through the core's Node or Swift binding, passing the count the person agreed to lose. The core removes the file, its logs, its downloaded files and its lock only if the file still holds exactly that count, and otherwise refuses with `unsent_differs` and the file's own count. [Devices](/devices#the-working-copy) explains the removal. +An app that lets a person discard the copy instead can remove it through the core's Node binding or the Swift SDK's [`WorkingCopy.remove(store:unsent:)`](/swift-sdk#remove-a-copy), passing the count the person agreed to lose. The core removes the file, its logs, its downloaded files and its lock only if the file still holds exactly that count, and otherwise refuses with `unsent_differs` and the file's own count. [Devices](/devices#the-working-copy) explains the removal. **A folder replaces its own store.** Each folder keeps a working copy of its own, with its record of which file is which item, which files went missing, which writes and placements the server refused and which notes were purged. A folder command that writes, such as `marfa folders push`, `marfa folders pull`, `marfa folders scan` or `marfa folders watch`, handles a store from another build itself: From 5795c6422dae4b41d706ef0c08cafcb87022b48d Mon Sep 17 00:00:00 2001 From: August Cayzer Date: Sun, 25 Oct 2026 19:05:56 +0000 Subject: [PATCH 6/6] docs(devices): leave the Swift SDK call to its own page --- content/docs/(overview)/devices.mdx | 2 +- content/docs/run/troubleshooting.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/(overview)/devices.mdx b/content/docs/(overview)/devices.mdx index 069fb3f0..66b0e473 100644 --- a/content/docs/(overview)/devices.mdx +++ b/content/docs/(overview)/devices.mdx @@ -39,7 +39,7 @@ The hydration **empties the copy first** if the instance's event log now ends be A copy made by an earlier build of the core can have a different shape. The device **refuses to open it** with `wrong_schema`, deletes nothing, and says how many writes the file holds that the server hasn't taken, because only the build that made it can send them. If some are of a kind the current build no longer sends, it names the kind. [Troubleshooting](/run/troubleshooting#staying-in-step) says what to do. An app built on the core's Node or Swift binding can replace a refused copy that holds nothing unsent with an empty one; the files the copy downloaded stay. A [folder](/run/sync-a-folder#move-to-a-newer-cli) can replace its own copy itself. -**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app passes the number of writes the server hasn't taken that it showed the person, through the core's Node binding or the Swift SDK's [`WorkingCopy.remove(store:unsent:)`](/swift-sdk#remove-a-copy). The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. +**An app can remove its working copy, with the unsent writes the person agreed to lose.** An app passes the number of writes the server hasn't taken that it showed the person, through the core's Node binding (`MarfaCore.removeStore`). The device removes the copy only when the copy holds exactly that number, whatever build made it: the file, its logs, the files it downloaded and its lock, and nothing else beside them. A symbolic link at one of those names is removed, never what it leads to. If the copy holds another number, or one the device can't read, the device refuses with `unsent_differs`, says the copy's own number, or that it can't read one, and leaves the copy, its logs, its downloaded files and its lock as they were. It refuses with `reading_handle` while anything else writes the copy, the app's own open copy included, so the app closes its copy first. A helper process that only reads the copy doesn't stop the removal, and keeps showing the old copy until it reopens. A folder's copy goes only with the folder, so the device refuses it with `invalid`. A path with no copy holds no unsent write, so removing it again finishes a removal that stopped part way. ## A write made offline diff --git a/content/docs/run/troubleshooting.mdx b/content/docs/run/troubleshooting.mdx index 25015263..6ed5fa1d 100644 --- a/content/docs/run/troubleshooting.mdx +++ b/content/docs/run/troubleshooting.mdx @@ -281,7 +281,7 @@ The message says how many writes the file holds that the server has not taken: w - **None.** Every write the file holds reached the server. Run `marfa device hydrate` to build a new working copy. An app built on the core's Node binding can call `replaceRefused` instead, and one built on the [Swift SDK](/swift-sdk#replace-a-refused-copy) can call `WorkingCopy.replaceRefused(store:server:fileBudget:)`: it replaces the old file with an empty copy, so what only the old file held, such as pins, is lost. The files the copy downloaded stay. - **Some.** Open the file with the build that made it, run `marfa device drain` to send the writes, and use `marfa device discard` on any it refuses that you don't want. Then hydrate a new copy with the newer build. Keep the old file until you have done this, because deleting it loses those writes. -An app that lets a person discard the copy instead can remove it through the core's Node binding or the Swift SDK's [`WorkingCopy.remove(store:unsent:)`](/swift-sdk#remove-a-copy), passing the count the person agreed to lose. The core removes the file, its logs, its downloaded files and its lock only if the file still holds exactly that count, and otherwise refuses with `unsent_differs` and the file's own count. [Devices](/devices#the-working-copy) explains the removal. +An app that lets a person discard the copy instead can remove it through the core's Node binding (`MarfaCore.removeStore`), passing the count the person agreed to lose. The core removes the file, its logs, its downloaded files and its lock only if the file still holds exactly that count, and otherwise refuses with `unsent_differs` and the file's own count. [Devices](/devices#the-working-copy) explains the removal. **A folder replaces its own store.** Each folder keeps a working copy of its own, with its record of which file is which item, which files went missing, which writes and placements the server refused and which notes were purged. A folder command that writes, such as `marfa folders push`, `marfa folders pull`, `marfa folders scan` or `marfa folders watch`, handles a store from another build itself: