Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions content/docs/guides/folder-safeguards.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ A folder deletes in both directions: a file you remove can send its item to the
| --- | --- |
| **You delete a file** | Moves its item to the bin after a five-second grace, so a save that replaces a file isn't taken for a delete. A sync that is running when you delete the file doesn't write it back. If another device changes what the file shows within the grace, the file comes back with that change instead. |
| **You move or rename a file in the folder** | Keeps its item, even when the move lands while a sync is running, and doesn't write the file back where it was. |
| **An item is moved to the bin, purged, or leaves the states the search holds** | Removes its file at the next sync. A file you changed since Marfa wrote it stays. |
| **An item stops matching the search** | Keeps its file, and edits to the file still reach the item. [`marfa folders unmatched`](/guides/keep-folders-running#remove-files-a-folder-keeps-but-no-longer-holds) lists these files. |
| **You move a file to another folder on the same Mac** | Treats it as the same item, so the folder it left doesn't take the move for a delete. |
| **An item is moved to the bin, purged, or leaves the states the query holds** | Removes its file at the next sync. A file you changed since Marfa wrote it stays. |
| **An item stops matching the query** | Keeps its file, and edits to the file still reach the item. [`marfa folders unmatched`](/guides/keep-folders-running#remove-files-a-folder-keeps-but-no-longer-holds) lists these files. |
| **You move a file to another folder on the same Mac** | Treats it as the same item, so the folder it left doesn't take the move for a delete. A new Markdown note moved before its `marfa_id` line is written becomes a second item in the destination folder and keeps every edit ([`folders/moved-arrival-markdown-new`](https://github.com/withmarfa/marfa/blob/main/conformance/spec/folders.md#foldersmoved-arrival-markdown-new)). |

A tier change **never deletes or moves an item to the bin**. If it takes an item outside the query's reach, the folder keeps its file unless another folder takes it.

The folder follows a Markdown note by its `marfa_id`. It follows any other file, such as a photo or a `.txt` note, by its file identity or its exact bytes, and keeps its record of the file until the other folder takes it. A `.txt` note that lands at a `.md` name is a new item.

Expand Down
2 changes: 1 addition & 1 deletion content/docs/guides/keep-folders-running.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ It writes and removes no file outside the folder's `.marfa` directory. If the in

## Remove files a folder keeps but no longer holds

When an item leaves a folder's search, for example because its type changed to one the search doesn't hold, the folder keeps its file instead of removing it. List those files:
When an item leaves a folder's query, for example because its type changed to one the query doesn't hold, the folder keeps its file instead of removing it. List those files:

<Terminal>

Expand Down
18 changes: 11 additions & 7 deletions content/docs/guides/sync-a-folder.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ marfa --socket /srv/marfa/data/control/marfa.sock keys create \
```

- **`system.folder`:** write creates the settings, and read lets `folders add` bind a directory to them.
- **The types the folder holds:** write on each type the search admits, here `core.note`. `core.file.*` covers `core.file` and every type under it.
- **The types the folder holds:** write on each type the query admits, here `core.note`. `core.file.*` covers `core.file` and every type under it.
- **`in-folder`:** write on this edge, which records where each file sits.

`folders add` is refused without read on `system.folder`, naming `system.folder:read`, and without write on `in-folder`, naming `edge.in-folder:write`. [Manage keys](/guides/manage-keys) explains each flag.
Expand All @@ -36,12 +36,12 @@ marfa --socket /srv/marfa/data/control/marfa.sock keys create \

### Create the folder's settings

The settings say which items the folder holds, through a search, and what a new file becomes. This folder holds every note:
The settings say which items the folder holds, through a query, and what a new file becomes. This folder holds every note:

<Terminal>

```bash
marfa folders create --title Notes --search '{"types":["core.note"]}'
marfa folders create --title Notes --query '{"types":["core.note"]}'
```

```text
Expand Down Expand Up @@ -84,7 +84,9 @@ Keep the directory out of iCloud Drive, other cloud-synced folders and network s

### Confirm the first sync

Check the plan first. **The search decides every item the folder takes in.** A search that names only a type holds every active and archived item of that type in the library tier, and the first sync writes all of them to disk. With no `types`, it holds every type the key reads except `system.*`. To narrow it, use `filter`, `tier`, `state` or `beneath`, as [list, filter and search](/guides/list-filter-search) describes.
Check the plan first. **The query decides every item the folder takes in.** A query that names only a type holds every active and archived item of that type in the library tier, and the first sync writes all of them to disk. With no `types`, it holds every type the key reads except `system.*`.

Set `tier: all` to write matching items from both the library and feed tiers as files. To narrow the query, use `filter`, `tier`, `state` or `beneath`, as [list, filter and search](/guides/list-filter-search) describes.

Then confirm, and sync once:

Expand Down Expand Up @@ -134,7 +136,9 @@ The lines between the `---` fences are the frontmatter. The text after them is t
- **Every other line:** a property.
- **`marfa_id` and `marfa_version`:** the item and the version the file was written from. Leave them to Marfa.

A new Markdown file needs no frontmatter. It takes its type and tier from the folder's `defaults` or its search, and its title from `defaults` or, without one, its file name. Once the instance creates the item, Marfa writes the frontmatter and `marfa_id` into the file. A `.txt` file is all body. Any other file the search admits, such as a photo, becomes a file item with its bytes uploaded as a blob, as [Handle files](/guides/handle-files) describes. A file item stays a file item at a `.md`, `.markdown` or `.txt` name, such as after a pull places it there or you rename it: its bytes go to the item as they are, and Marfa doesn't read them as a note.
A new Markdown file needs no frontmatter. It takes its type and tier from the folder's `defaults` or its query, and its title from `defaults` or, without one, its file name. With `query.tier: all`, a new file without an explicit tier uses `defaults.tier` or, without one, `library`. Once the instance creates the item, Marfa writes the frontmatter and `marfa_id` into the file.

A `.txt` file is all body. Any other file the query admits, such as a photo, becomes a file item with its bytes uploaded as a blob, as [Handle files](/guides/handle-files) describes. A file item stays a file item at a `.md`, `.markdown` or `.txt` name, such as after a pull places it there or you rename it: its bytes go to the item as they are, and Marfa doesn't read them as a note.

## Edit on either side

Expand Down Expand Up @@ -168,7 +172,7 @@ Each computer writes the folder's settings out as `.marfa/folder.yaml`:
folder: 01a1232d-949d-758c-b244-70057174b96a
version: 1
title: Notes
search:
query:
types:
- core.note
```
Expand Down Expand Up @@ -205,7 +209,7 @@ import Marfa
let server = Server(url: serverURL, key: key)
let copy = try await WorkingCopy.open(store: storeURL, server: server)
let folder = try await copy.createFolder(
FolderSettings(title: "Notes", search: FolderSearch(types: ["core.note"])))
FolderSettings(title: "Notes", query: FolderQuery(types: ["core.note"])))

let folders = Folders(server: server)
let directory = URL(filePath: "/Users/sam/Notes")
Expand Down
25 changes: 12 additions & 13 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -6270,7 +6270,7 @@
],
"description": "An error response."
},
"FolderSearch": {
"FolderQuery": {
"type": "object",
"properties": {
"types": {
Expand All @@ -6282,14 +6282,13 @@
"description": "The types the folder holds, each with its subtypes. Empty or absent holds every type the machine's key can read, except `system.*`."
},
"tier": {
"allOf": [
{
"$ref": "#/components/schemas/Tier"
},
{
"description": "The tier the folder holds. Absent means `library`."
}
]
"type": "string",
"enum": [
"library",
"feed",
"all"
],
"description": "The tiers the folder holds: `library`, `feed`, or `all` for both. Absent means `library`."
},
"state": {
"type": "array",
Expand Down Expand Up @@ -34986,8 +34985,8 @@
"maxLength": 500,
"description": "The folder's title."
},
"search": {
"$ref": "#/components/schemas/FolderSearch"
"query": {
"$ref": "#/components/schemas/FolderQuery"
},
"defaults": {
"$ref": "#/components/schemas/FolderDefaults"
Expand Down Expand Up @@ -35474,8 +35473,8 @@
"maxLength": 500,
"description": "A new title for the folder."
},
"search": {
"$ref": "#/components/schemas/FolderSearch"
"query": {
"$ref": "#/components/schemas/FolderQuery"
},
"defaults": {
"$ref": "#/components/schemas/FolderDefaults"
Expand Down
2 changes: 1 addition & 1 deletion src/generated/GLOSSARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ Access is read the same way wherever a person sees it: on the pages where they a
| **file budget** | The most bytes the file cache is to hold: a byte count a caller sets, none while every file is kept, or by default the lesser of the larger of 2 GiB and a tenth of the volume, and the bytes held plus the volume's free bytes less a twentieth of the volume, never below 256 MiB, and 2 GiB where the volume cannot be read. Bytes a queued upload names, and bytes whose path or file a caller in the same process is still waiting for, are never taken to fit it. |
| **drain** | To send a device's queued writes to the server and record each answer as the write's verdict. Also the noun: a drain is one such pass over the queue, as `marfa device drain` runs it. A write that met a network rather than an answer waits for the next drain. Catch-up, which brings the server's changes into the working copy, is a different act. |
| **forget** | To clear a device's answered writes from its queue: `marfa device forget`, and `forgetAnswered()` in the Swift SDK and the Node module. Given ids (`--id` on the command, `forgetAnsweredIds()` in the libraries), it clears only the writes named. The contract's rules say "clear" for the same act, as in `queue-and-verdicts/clear-answered`. `marfa keys forget` keeps the ordinary sense: it removes the credential kept for a server. |
| **folder** | A directory on a machine bound to a `system.folder`, whose settings every machine bound to it shares and each writes out as one editable file, `.marfa/folder.yaml`: a search that decides what the folder holds, the defaults a new file takes, and include and ignore lists. A device like any other (the same working copy, the same queue, the same verdicts) with a filesystem as its face: a file in it is an item on the server, and an item its search matches is a file in it. |
| **folder** | A directory on a machine bound to a `system.folder`, whose settings every machine bound to it shares and each writes out as one editable file, `.marfa/folder.yaml`: a query that decides what the folder holds, the defaults a new file takes, and include and ignore lists. A device like any other (the same working copy, the same queue, the same verdicts) with a filesystem as its face: a file in it is an item on the server, and an item its query matches is a file in it. |
| **placement** | Where an item's file sits in a folder: its `in-folder` edge to the folder's `system.folder`, whose `path` names the file from the folder's root. It never decides what a folder holds, and it is never written in a file. |
| **registry** | The folder registry: one file on a machine listing its folders, each by its directory and the `system.folder` it follows, so each folder can find the others and a file moved or copied between two of them is told apart. It is never a folder's store, and `MARFA_FOLDER_REGISTRY` names another file in its place. The type registry is the server's catalog of types (`types.md`). |
| **embed** | A file an item's body shows inside it, `![](path)` or `![[file]]`: that file item's `attached-to` edge to the item, in a folder's Markdown file and in a body written through a working copy alike. In a folder, the file's `has-attachment` line lists only what the body does not embed, and the embedded file travels with the file showing it and is written where the link says. It names a file where a file answers to it or its extension names a MIME type; anything else, `![[A note]]` say, is an embed of a note, text in the body and neither a link nor an edge. |
Expand Down
8 changes: 4 additions & 4 deletions src/generated/catalog/builtin-catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -1648,13 +1648,13 @@
"description": "The folder's name",
"required": true
},
"search": {
"query": {
"type": "object",
"description": "Which items the folder holds. `types`: type identifiers, each with its subtypes; empty or absent holds every type the folder's key reads. `tier`: `library` or `feed`, the one tier the folder holds; absent is `library`. `state`: `active`, `archived` or both; absent is both. `filter`: an expression in the listing grammar's `filter`. `beneath`: an item id; the item and everything under it by `parent-of`."
"description": "Which items the folder holds. `types`: type identifiers, each with its subtypes; empty or absent holds every type the folder's key reads. `tier`: `library`, `feed`, or `all` for both tiers; absent is `library`. `state`: `active`, `archived` or both; absent is both. `filter`: an expression in the listing grammar's `filter`. `beneath`: an item id; the item and everything under it by `parent-of`."
},
"defaults": {
"type": "object",
"description": "What a new file takes where its frontmatter leaves a blank, never applied to an edit: `type`, `tier`, `properties`, `tags`, and `edges` as a map from edge type to item ids, each edge running from the new file to the item named except `parent-of`, which runs from the item named so the new file is its child; a folder whose search follows an edge gives every new file that edge."
"description": "What a new file takes where its frontmatter leaves a blank, never applied to an edit: `type`, `tier`, `properties`, `tags`, and `edges` as a map from edge type to item ids, each edge running from the new file to the item named except `parent-of`, which runs from the item named so the new file is its child; a folder whose query follows an edge gives every new file that edge."
},
"include": {
"type": "array",
Expand Down Expand Up @@ -1794,7 +1794,7 @@
{
"id": "in-folder",
"label": "In folder",
"description": "Where an item's file sits in a folder: the source is the item, the target the folder's `system.folder`, and `path` the file's path relative to the folder's root. Placement only; a folder's search alone decides what it holds, and the edge is never written in a file's frontmatter. An item may sit in any number of folders. Revoking or removing the folder leaves its items in place, and a revoked folder takes no new placement.",
"description": "Where an item's file sits in a folder: the source is the item, the target the folder's `system.folder`, and `path` the file's path relative to the folder's root. Placement only; a folder's query alone decides what it holds, and the edge is never written in a file's frontmatter. An item may sit in any number of folders. Revoking or removing the folder leaves its items in place, and a revoked folder takes no new placement.",
"cardinality": "many-to-many",
"source_type_constraints": [
"*"
Expand Down
2 changes: 1 addition & 1 deletion src/generated/catalog/edges.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Source item is a member of the target container. The target may be any type decl

## in-folder

Where an item's file sits in a folder: the source is the item, the target the folder's `system.folder`, and `path` the file's path relative to the folder's root. Placement only; a folder's search alone decides what it holds, and the edge is never written in a file's frontmatter. An item may sit in any number of folders. Revoking or removing the folder leaves its items in place, and a revoked folder takes no new placement.
Where an item's file sits in a folder: the source is the item, the target the folder's `system.folder`, and `path` the file's path relative to the folder's root. Placement only; a folder's query alone decides what it holds, and the edge is never written in a file's frontmatter. An item may sit in any number of folders. Revoking or removing the folder leaves its items in place, and a revoked folder takes no new placement.

**written_at:** `none`

Expand Down
Loading
Loading