@@ -1,15 +1,191 @@
11# SPEC — a tree-shaped plain-text notes and task manager for the terminal
22
3−Status: draft 0.40 · 2026-09-26
4−Name: fold. The binary is `fold`; the crates are `fold-*` (§15.1).
3+Status: draft 0.38 · 2026-09-12
4+Working name: not chosen yet. This document uses `notes` as the binary name; rename freely.
55Language: Rust · TUI: ratatui · Sync: Syncthing · History: file versioning on one node
66
7+Changes in 0.38: no external editor in 1.0. `E`, the inline-frontmatter spelling, the
8+`inline` render mode and `$EDITOR` are gone; the built-in editor is the only editor (§10.6,
9+§17). Frontmatter never appears as text anywhere.
10+
11+Changes in 0.37: `E` is scoped to the selected node and replaces its subtree wholesale on
12+exit. The text handed to `$EDITOR` carries every nested block's frontmatter **inline**, as a
13+`---` block under its title line, `id` included, so blocks come back matched exactly by id;
14+no line diff, no heuristics (§4.4, §10.6). `e` is unchanged.
15+
16+Changes in 0.36: editing is continuous. The built-in editor is one text over the resolved
17+subtree with no locked lines, no dimming and no commit key: every line carries its owning
18+block invisibly, edits apply to whichever block the cursor is in, and blocks are saved
19+automatically when the cursor leaves them, after a short pause, and on exit (§5.2, §10.6).
20+The *make block* / *cut* distinction is not visible while typing.
21+
22+Changes in 0.35: vocabulary. *Document*, *part* and *cut* were one concept and are now
23+**block**: a node with an id, a file and properties, edited alone. *Edge* is **embed**,
24+which is what `![[id]]` is. The *document pane* is the *reading pane*. Storage sections
25+still say "file" where they mean bytes on disk.
26+
27+Changes in 0.34: every edit changes exactly one file. The editor still shows the whole
28+resolved subtree, but only the lines owned by one **part** — the file the cursor node lives
29+in — are editable; the rest is context, shown but locked, and the status line names the
30+part. Splice is single-file again; the multi-file distributor is gone (§5.2, §10.6).
31+
32+Changes in 0.33: properties never appear in text. The resolved rendering the user reads and
33+edits has no frontmatter and no property blocks; properties are edited in a form (`a`,
34+§10.6) and shown as a dimmed header in the document pane. Splice never touches
35+frontmatter.
36+
37+Changes in 0.32: files are invisible when editing. The user edits a slice of one large
38+document: `e`/`E` open the **resolved** subtree — documents inlined, no `![[id]]`, no ids —
39+with each node's properties shown as a `---` block under its title line (§4.4). Splice
40+parses the result and distributes it back to whichever files own the text, cutting,
41+moving and trashing files as the text requires (§5.2). Files are storage, not UI.
42+
43+Changes in 0.31: one editing model. `e` opens the selected subtree's Markdown —
44+`render(cursor, 1, false)`, frontmatter included — in an editor and splices it back; `E`
45+does the same in `$EDITOR`. Title editing, the block editor and the property editor are
46+gone: a title, a property, a body are all just text in the subtree (§10.6).
47+
48+Changes in 0.30: the `:` command line is a command palette — a fuzzy picker over every
49+action, showing its key, prompting for arguments after selection (§10.9). No command syntax
50+to learn or type.
51+
52+Changes in 0.29: frontmatter is text. No value types, no `tags` key, no `--set`/`--due` on
53+capture, no relative date forms. The app knows five keys — `id`, `todo`, `due`, `done`,
54+`conflict` — reads them as strings, validates the two dates as ISO when it writes them, and
55+preserves every other line verbatim (§3.2, §4.4).
56+
57+Changes in 0.28: the query language, saved views, query blocks, the `v` picker, result
58+views, `notes query` and `--json` are out of 1.0 (§17). `query` fences are reserved. The
59+fresh vault is `# Inbox` alone. The filter box (§10.5) is the only search.
60+
61+Changes in 0.27: no config file. `config.toml` and its seven keys are gone; the vault comes
62+from `$NOTES_VAULT` or the working directory, the editor from `$EDITOR`, and the rest is
63+fixed or a session toggle (`zd`, `zr`). The trash is the only thing outside the vault (§14).
64+
65+Changes in 0.26: the CLI is six commands: `notes`, `capture`, `query`, `check [--fix]`,
66+`merge`, `trash`. `init`, `config`, `view`, `ls`, `set`, `unset`, `cut`, `archive`,
67+`canonicalize` and `adopt` are gone; the TUI does each of them, and `check --fix` absorbs
68+canonicalize (§13). Foreign `.md` files can no longer be adopted; paste their text instead.
69+
70+Changes in 0.25: more removals. The cancelled task state (`[-]`, `todo: cancelled`,
71+`cancelled:`, `X`) and the `priority` and `repeat` keys are gone. No tabs, no `:sort`, no
72+create-from-filter-box, no `notes inbox` / `props` / `id`, no `keys.*` rebinding or theme,
73+no `Ctrl-w`. The `Waiting` default view is gone and the `Inbox` view is now `Queue`.
74+`notes render`, `:export` and the `--plain` lowering are removed: there is no export in 1.0
75+(§17); `render` remains an internal function.
76+
77+Changes in 0.24: seven removals. One edge shape, `![[id]]`, and a document's file starts
78+with the node as spelled — no more `#`-always rule (§4.7, §4.9). No in-session three-way
79+merge: a splice that finds its span changed writes a conflict document like any other
80+conflict (§5.2). No fuzzy title matching in merge (§12.4). Files without a valid `id` are
81+ignored, not "unmanaged" (§4.1). Archive is plain refile under `Archive` (§6.5). No
82+`--as dir/`: the vault is flat and prefixes are unique vault-wide (§6.4). No `conflicts/`
83+directory and no `conflict_with` key: conflict copies are ordinary documents marked
84+`conflict:`, stitched in next to ours (§12.4).
85+
86+Changes in 0.23: no cache and no per-device state. The index is rebuilt from the vault on
87+every start; there are no shadow copies, so sync-conflict merging is two-way (§11.3, §12.3).
88+The in-session splice conflict keeps its three-way base in memory.
89+
90+Changes in 0.22: `uncut` removed. A cut is permanent; a document stays a file until deleted.
91+Conflict resolution copies text instead of uncutting (§12.5).
92+
93+Changes in 0.21: a document's task state is the frontmatter key `todo: open | done |
94+cancelled`, not a checkbox on its title line. Checkboxes remain the syntax for nodes that
95+are not files. Cut and uncut convert between the two (§4.5, §8).
96+
97+Changes in 0.20: the separator between id prefix and name in filenames is `~`:
98+`racfer~order-new-switch.md`. Words inside the prefix stay `-`-joined.
99+
100+Changes in 0.19: filename prefixes are unique on their own, ignoring the title: no two files
101+in a directory share a prefix, whatever their names (§6.4).
102+
103+Changes in 0.18: ids are 64 bits, four `@p` words (`racfer-hattes-dozzod-binwes`). Filenames
104+start with the shortest prefix of the id — one word by default — that is unique in the
105+directory, and grow a word at a time when a collision is found, locally or at merge (§6.4,
106+§12.2). The `-2` suffix is gone.
107+
108+Changes in 0.17: filenames are `<word>-<name>.md`, where `<word>` is the first word of the
109+id (`racfer-order-new-switch.md`). Same-title documents no longer collide across devices
110+except at 1 in 65,536; the merge engine treats an id mismatch as a name collision, not a
111+conflict (§6.4, §12.2).
112+
113+Changes in 0.16: `@key(value)` annotations removed. All metadata is frontmatter again, so
114+only documents carry it: `done:` and `cancelled:` are stamped on task documents, inline
115+tasks record state only, and nothing records creation time. Conflict copies are documents
116+in `conflicts/` again, with `conflict:` frontmatter. `@` appears only in the query language.
117+
118+Changes in 0.15: the "no syntax of our own" restriction is lifted (§1). Consequences:
119+one structural node kind with two spellings, either nestable under either (§3.1);
120+heading levels unbounded, `§5.3` level overflow gone; any node can be a task, headings
121+included (§4.5); a document's task state lives on its own title line, edges carry none
122+(§4.7, §4.9); the app stamps `@created`, `@done`, `@cancelled` as title-line annotations on
123+any node (§3.2); the id moves from the filename into `id:` frontmatter, filenames are plain
124+slugs, edges are `![[id]]` (§3.4, §6.4); `|` is allowed in titles; merge conflicts are
125+inline `@conflict` siblings, `conflicts/` is gone (§12.4). `notes render --plain` lowers
126+the format to standard Markdown for export (§5.1).
127+
128+Changes in 0.14: `[[wiki-links]]` between nodes removed from 1.0 — no link syntax, resolution,
129+rename rewriting, backlinks or tombstones. `[[…]]` in titles and bodies is opaque text,
130+preserved verbatim and reserved (§4.6, §17). Edges (`![[id-name]]`) and external
131+`[text](url)` links are unchanged.
132+
133+Changes in 0.13: capture appends under a `## <today>` child of `Inbox`, created on demand.
134+Fixed behaviour, not a setting; the day title is ISO `YYYY-MM-DD`.
135+
136+Changes in 0.12: every path component the app writes is lowercase — file names, directory
137+names (including `--as`), temp and trash names. Titles keep their case; resolution is
138+case-insensitive.
139+
140+Changes in 0.11: no vault settings at all. Capture always goes to the level-1 section titled
141+`Inbox`, created if missing. `root.md` has no frontmatter in a fresh vault.
142+
143+Changes in 0.10: `done_precision` and `conflicts` removed. `done:` records a date; conflict
144+documents live in `conflicts/`. `inbox` is the only vault setting.
145+
146+Changes in 0.9: `archive` and `:archive-done` removed — done items stay where they are,
147+a display toggle hides them, and `:clear-done` trashes them when a list is finished.
148+(An `archive` section as a plain refile target returns later — see §6.5.)
149+`suggest_bytes` removed — the app never suggests cuts. Three vault settings remain:
150+`inbox`, `done_precision`, `conflicts`.
151+
152+Changes in 0.8: `capture` renamed `inbox` and is a plain link target (no date template);
153+`children: files` removed, so there are no section settings and no derived directories;
154+`cut`, `rename`, `transliterate` and `id_words` removed — cuts are property-triggered or
155+manual, filenames follow titles, names keep Unicode, ids are two words. Five vault settings
156+remain: `inbox`, `archive`, `done_precision`, `conflicts`, `suggest_bytes`.
157+
158+Changes in 0.7: `dir` and `sort` removed. The only section setting is `children: files`;
159+its files go in a directory named after the section. New children are always appended.
160+
161+Changes in 0.6: `config.toml` is gone. Vault settings are `root.md`'s frontmatter; section
162+settings (`children`) are the section's own frontmatter; saved views are query
163+blocks. A fresh vault is initialised with a `root.md` that lists every setting at its default,
164+commented. Only an optional, device-local file for UI preferences remains.
165+
166+Changes in 0.5: the journal is no longer a concept. Capture appends to a configurable,
167+date-templated target (default `Inbox/{date}`), and "children of this section are always
168+files" is a general `[[cut.auto]]` rule. Journal days become ordinary documents; the id-less
169+filename shape and the date resolution step are gone.
170+
171+Changes in 0.4: ids are two-word phonemic names in Urbit's `@p` syllable scheme
172+(`racfer-hattes`, 32 bits) instead of 8-character base32.
173+
174+Changes in 0.3: document files are named `<id>-<name>.md`, where the id is an 8-character
175+random base32 string and the name is a slug of the title. Links and edges resolve by id, so
176+they survive renames and stale rewrites; the name is decoration for humans and other tools.
177+
178+Changes in 0.2: all inline attribute syntax (`@key(value)`) removed. Properties exist only as
179+YAML frontmatter of a file, so a node that needs properties becomes a file ("document").
180+Items can be cut into documents; their checkbox stays on the edge in the parent list.
181+`@id` removed; the file slug is the identity. Merge conflicts are stored as documents in
182+`conflicts/`. Task-state syntax `[ ]`, `[x]`, `[-]` is unchanged.
183+
7184---
8185
9186## 1. Purpose and principles
10187
11−A mouse-first TUI — every action is a click, a drag or the wheel away, and every one also
12−has a key — for a single body of notes stored as Markdown. There is one organizing
188+A keyboard-driven TUI for a single body of notes stored as Markdown. There is one organizing
13189primitive — the **subtree** — and one organizing verb — **refile** (move a subtree under a
14190different parent). Everything else (inbox, tasks, projects, index) is a view over the
15191same tree.
@@ -17,8 +193,8 @@ same tree.
17193Principles, in priority order:
18194
191951. **The files are the database.** Every fact the app knows is recoverable from the Markdown.
20− There are no caches, no configuration, and no per-device state beyond the trash and the
21− remembered view (§10.1). The app never writes a byte it didn't need to change.
196+ There are no caches, no configuration, and no per-device state beyond the trash. The
197+ app never writes a byte it didn't need to change.
221982. **Markdown-shaped, with our own syntax where it pays.** Headings, bullets, checkboxes,
23199 fences and YAML frontmatter are used as they are. On top of them the format owns a small,
24200 closed set of things a standard renderer will not understand: `![[id]]` embeds, headings
@@ -45,22 +221,20 @@ Principles, in priority order:
45221|---|---|
46222| Vault | A directory containing `root.md` and, optionally, block files. |
47223| Tree | The single logical outline formed by `root.md` plus all block files stitched in. |
48−| Node | One thing in the outline: a title line and its children. Spelled as a section or an item. |
49−| Children | A node's ordered content: text children and child nodes, interleaved (§3.1). |
50−| Text child | A run of lines among a node's children that is not a node: prose, code, a quote (§3.3). |
224+| Node | One thing in the outline: a title line, a body, children. Spelled as a section or an item. |
51225| Section | A node spelled as an ATX heading `#`, `##`, … (no upper bound). |
52226| Item | A node spelled as a `- ` bullet. |
53227| Spelling | Whether a node is written as a heading or a bullet. Presentation only (§3.1). |
54−| Task | A node with a checkbox after its marker — block or not. |
228+| Task | A non-block node with a checkbox after its marker, or a block with a `todo` key. |
55229| Block | A node with its own identity and file. Only blocks have properties; every edit changes exactly one block. |
56230| Id | A random four-word phonemic name (`racfer-hattes-dozzod-binwes`) in a block's `id:` frontmatter; its stable identity. |
57231| Name | The slug of a block's title. With a prefix of the id in front, its filename. Decoration, not identity. |
58232| Property | A key in a block's YAML frontmatter. |
59−| Body | A node's leading text children: what comes before its first child node. |
233+| Body | Block content belonging to a node before its first child. |
60234| Zoom | Viewing a node as a standalone block via `render(node, base)`. |
61235| Splice | Writing an edited zoomed block back into its source span. |
62236| Make a block | Giving a node an id and its own file, leaving an embed in the parent. |
63−| Embed | `![[id]]` alone on a line (or as a heading, `## ![[id]]`) at the node's position in the parent. |
237+| Embed | `![[id]]` alone on a line at the node's position in the parent. |
64238| Span | A byte range in a file. Every node knows its span. |
65239| Path | A node's address by ancestor titles: `Homelab/NAS/ZFS layout`. |
66240
@@ -78,64 +252,20 @@ Section — spelled as a heading; may hold prose; children of either spelling
78252Item — spelled as a bullet; may hold note lines; children of either spelling
79253```
80254
81−A node's **children** are an ordered list in which two things interleave: **child nodes**
82−and **text children** (§3.3), in the order the file has them:
83−
84−```
85−Trip ← section
86−├─ text "Plan below."
87−├─ item book flights
88−├─ item book hotel
89−└─ text "Budget is tight, so check both before paying."
90−```
91−
92255Rules:
93256
94257- Any node may be the child of any node. A heading under a bullet is a section whose parent
95258 is an item; a bullet under a heading is an item whose parent is a section.
96−- **Ordering rule.** Every node's children match
97−
98− ```
99− children := (text | item)* section*
100− ```
101−
102− Markdown has no way to close a heading: anything written after a section child — text or
103− an item at that position — belongs to that section. So once a section child appears, only
104− section children follow it. The parser produces nothing else; every verb that places a
105− node (§6.5, §10.3, §12.4) keeps the rule by **clamping**: an item goes no later than just
106− before its new parent's first section child, and a section no earlier than just after its
107− last item child. The *boundary* of a node's children is the position between its last
108− item or text child and its first section child.
109−
110− **What the rule costs.** A node cannot have items or text after a section child: under
111− `# Trip` with a `## Budget` subsection, there is no way to put another bullet or paragraph
112− back under Trip after Budget. Everything written there is Budget's. Plain Markdown cannot
113− express it either, so the format accepts the limit rather than invent a closing syntax
114− no other tool would read. The practical consequences are that `>`, `<`, paste, refile and
115− capture sometimes place an item earlier than asked (before the first section), `J` / `K`
116− refuse to move an item past a section, and `~` moves the node it respells. The TUI says
117− so in the status line whenever the rule, not the user, chose the position; none of these
118− is a bug. The workaround is the obvious one:
119− put the later items in a section of their own (`## Also`), or keep sections last.
120−- **Text children are content, not outline.** They are not rows in the outline pane, not
121− targets, never blocks or tasks, and are never moved on their own. A verb that moves,
122− deletes or refiles a node moves exactly that node's own lines; the text children around it
123− stay where they are, with the siblings they were between. They move only with the node
124− that contains them.
125259- Heading level and indent are **derived from position**, never stored:
126260 `level(n) = 1 + number of section ancestors of n`, and `indent(n) = 2 × number of item
127261 ancestors of n`. A section under an item is written at that item's child indent with its
128262 own level. There is no upper bound on levels.
129−- Spelling is presentation: `~` toggles it (§10.3) and nothing else about the node changes.
130− Both spellings can be tasks, hold text, be refiled anywhere, and be made into blocks. The
131− one consequence of spelling is position: under the ordering rule a node respelled by `~`
132− moves to its parent's boundary, so its siblings keep their parent.
263+- Spelling is presentation: `~` toggles it (§10.3) and nothing else changes. Both spellings
264+ can be tasks, hold bodies, be refiled anywhere, and be made into blocks.
133265- Sibling order is block order and is meaningful.
134266- Any node may be a **block** (the root of its own file). Its file starts with the node
135− as it is spelled — a heading or a bullet — and that is where its spelling is defined
136− (§4.9). The embed repeats it in its form (§4.7) so the parent file parses into the right
137− shape on its own; the app keeps the two in step (`~` rewrites both), and where they
138− disagree the file wins and `fold check` reports the embed.
267+ as it is spelled — a heading or a bullet — so the file is the only place its spelling
268+ lives (§4.9).
139269
140270### 3.2 Properties
141271
@@ -159,6 +289,7 @@ Reserved keys (the app assigns semantics; users may still read/write them by han
159289| Key | Value | Meaning |
160290|---|---|---|
161291| `id` | four `@p` words | The block's identity (§3.4). Present on every managed file; never on `root.md`. |
292+| `todo` | `open` \| `done` | Makes the block a task and holds its state (§4.5). Absent means not a task. |
162293| `due` | `YYYY-MM-DD` | Task due date; shown in the outline. |
163294| `done` | `YYYY-MM-DD` | Written when a task block is checked; removed when unchecked. |
164295| `conflict` | `<device> <timestamp>` | Marks a "theirs" copy written by the merge engine (§12.4). |
@@ -171,32 +302,16 @@ nothing reorders it on insert.
171302Everything else (`tags`, `priority`, `est`, `waiting`, `since`, `author`, `source`, …) is
172303user-defined, and the app never looks at it.
173304
174−### 3.3 Text children
305+### 3.3 Bodies
175306
176−A text child is a run of consecutive raw lines, opaque to the tree, between two structural
177−points: the title line, a child node, or the end of the node. The parser only needs to know
307+A body is an ordered list of raw lines, opaque to the tree. The parser only needs to know
178308enough to find the *next title line*: it tracks fenced code blocks (```` ``` ```` and `~~~`,
179309any indent) so that `#` or `- ` inside a fence never starts a node. Paragraphs, quotes,
180−tables, images, HTML and code are all text.
181−
182−A text line belongs to the deepest open node whose region it reaches:
183−
184−- an item's region is lines indented at least `indent + 2`;
185−- a section's region is lines at or beyond its own indent;
186−- the root's region is everything.
187−
188−So a line at column 0 after `- b` under `# Trip` is Trip's text, not b's: it ends the list.
189−(CommonMark would read an unindented line right after a list item with no blank line between
190−as a lazy continuation of that item; this format does not. Canonical form therefore always
191−writes a blank line there (§4.2), and then both agree.)
192−
193−A blank line belongs to the text run it is in, or — between nodes — to the deepest open node
194−before it: the blank line after `- a` is `a`'s trailing separator. Blank lines are therefore
195−never lost or invented when a node is rendered or moved.
310+tables, images, HTML and code are all body.
196311
197−The **body** of a node is its leading text: the text children before its first child node.
198−It is what the reading pane shows under the title before any child, and what "a node with a
199−body" means elsewhere in this spec.
312+For items, body lines are those indented at least `indent + 2` that are not child title
313+lines (bullets or headings). For sections, body is everything from the title line to the
314+first child, at the section's own indent.
200315
201316### 3.4 Identity and addressing
202317
@@ -208,19 +323,7 @@ when the block is created and stored as the `id` key of its frontmatter. Its fil
208323directory uses as its prefix — usually one — then the slug of its title
209324(`racfer~order-new-switch.md`). The prefix alone identifies the file; the name is for humans. The filename is decoration that any tool may change and the
210325app will repair (§6.4). The one exception is `root.md`, which has no
211−id. A non-block node has no stored identity. Two things stand in for one, and they are
212−different on purpose:
213−
214−- **Typed targets** (below) name a node by titles. Titles are what a person can type, and a
215− title that matches twice is an ambiguity error, never a guess.
216−- **Tracking** — re-attaching the cursor, folds and a verb's result across a re-parse —
217− uses the node's **key**: the block whose file holds it (none for `root.md`), then one step
218− per level down from that file's root, each step the title *and the node's ordinal among
219− same-titled siblings*. Two siblings with one title, or two empty new nodes, have
220− different keys. Keys are never shown and never typed.
221−
222−Verbs never find a node again by its title path: a verb that re-parses finds its result by
223−key, by id, or by position.
326+id. A non-block node's identity is its **path**.
224327
225328Embeds reference the id alone (`![[racfer-hattes-dozzod-binwes]]`), so nothing in the vault
226329depends on a filename and a rename touches only the renamed file.
@@ -235,19 +338,17 @@ Resolution order for a command target (the *refile*, *go to* and *make block* pr
235338 (`2026-09-10`) resolve this way; there is nothing special about them.
236339
237340Ambiguous or missing targets are an error for the command that named them; broken or
238−duplicate embeds are diagnostics in `fold check` (§15.7).
341+duplicate embeds are diagnostics in `notes check` (§15.7).
239342
240−Renaming a block's title does not rename its file (§6.4); a stale name is harmless, since
241−nothing references the filename.
343+Renaming a block's title renames its file (§6.4). Nothing else references the filename.
242344
243345Titles may not contain `/` (the path separator). `[` and `]` are discouraged.
244346
245347### 3.5 Derived facts
246348
247349Computed by the index, never stored in files: heading level, indent, path, file, span,
248−open/done task counts per subtree (`3/7` in the outline; a conflict copy's tasks, §12.4,
249−are not counted above it), per-file mtime. Per-node timestamps are not tracked by the app
250−(§12.6).
350+open/done task counts per subtree (`3/7` in the outline), per-file mtime. Per-node
351+timestamps are not tracked by the app (§12.6).
251352
252353---
253354
@@ -266,7 +367,7 @@ The vault is flat: every block sits beside `root.md`. Only `root.md` and `.md` f
266367whose frontmatter carries a valid `id` (§6.4) are parsed. Everything else — subdirectories,
267368non-Markdown files, `.md` files without an id — is ignored: never parsed, never written,
268369never deleted. There is no import: to bring a foreign `.md` file in, paste its text into
269−a node (or `fold capture < file.md`) and let the app assign ids as it makes blocks. `fold check`
370+a node (or `notes capture < file.md`) and let the app assign ids as it makes blocks. `notes check`
270371lists ignored `.md` files so nothing is forgotten in the vault.
271372
272373There is no configuration and no configuration file. The inbox is the section titled
@@ -305,16 +406,14 @@ The app **reads** any CommonMark it can make sense of and **writes** this subset
305406 or bullets alike. `[X]` is accepted on read; `[-]` is read as `[x]`.
306407- Exactly one blank line between a node's body and its first child, and between sibling
307408 sections. Loose/tight lists are preserved as found.
308−- A blank line before any text child that follows a child node (§3.3), so that CommonMark
309− readers agree the text is not part of that node.
310409
311410Canonicalization is **lazy**: a node is rewritten in canonical form only when it is touched
312−(edited, spliced, moved). `fold check --fix` (or *canonicalize* in the palette) rewrites
411+(edited, spliced, moved). `notes check --fix` (or *canonicalize* in the palette) rewrites
313412everything at once.
314413
315414**What is ours.** A standard Markdown renderer will mishandle exactly these four things:
316−`![[id]]` embeds (bare or as a heading), heading levels beyond six, checkboxes on headings,
317−and headings indented under bullets. Nothing else in a vault is non-standard.
415+`![[id]]` embeds, heading levels beyond six, checkboxes on headings, and headings indented
416+under bullets. Nothing else in a vault is non-standard.
318417
319418### 4.3 Title lines
320419
@@ -328,7 +427,7 @@ title := any text not starting with whitespace; may not contain "/"
328427
329428There is no attribute syntax. An `@word` or `key:: value` in a title or body is text, and
330429so is `[[…]]` (reserved, §4.6).
331−A title line with an empty title is invalid; `fold check` reports it, the TUI won't create it.
430+A title line with an empty title is invalid; `notes check` reports it, the TUI won't create it.
332431
333432### 4.4 Frontmatter
334433
@@ -345,34 +444,26 @@ Inside the app, frontmatter is storage: the reading pane shows a block's propert
345444dimmed header computed from the index, the property editor (`a`, §10.6) changes them, and
346445the built-in editor's text (§5.1, `resolve_blocks = true`) contains none.
347446
348−Frontmatter never appears as text to the user.
447+Frontmatter never appears as text to the user. (An inline `---`-under-the-title spelling
448+was designed for an external editor and is set aside with it, §17.)
349449
350450### 4.5 Tasks
351451
352−Any node can be a task, and there is one way to write it: a **checkbox after the marker on
353−its title line**. `- [ ]` and `## [ ]` are both open tasks; `[x]` is done. A block is no
354−different — its checkbox is on its own title line, in its file:
355−
356−```markdown
357−---
358−id: racfer-hattes-mislup-nodrys
359−due: 2026-09-20
360−---
361−
362−- [ ] Order new switch
363−```
452+Any node can be a task. Where the state is written depends on whether the node is a file:
364453
365−What a block adds is a completion record: checking it writes `done: <date>` to its
366−frontmatter, and unchecking removes the date. A plain task has no frontmatter and so no
367−date; that is the whole difference. Because the state is always on the title line,
368−`rg '\[ \]'` over the vault is the complete open-task list.
454+- A node that is **not** a block is a task iff it has a checkbox after its marker.
455+ `- [ ]` and `## [ ]` are both open tasks; `[x]` is done. That is all the state it has.
456+- A **block** is a task iff its frontmatter has `todo:`, whose value is the state:
457+ `open` or `done`. Its title line in the file carries no checkbox. Checking writes
458+ `todo: done` and `done: <date>`; reverting sets `todo: open` and removes the date.
369459
370460There is no cancelled state. A task that will never be done is either checked off or
371461deleted.
372462
373−Embeds carry no state: the checkbox travels with the title line into the block's file when
374−it is made (§6.1). A task section has its own state *and* the derived count of its subtree.
375−See §8.
463+Embeds carry no state. Cutting a checkbox task turns the checkbox into `todo:` (§6.1). A
464+checkbox on a block's own title line is
465+accepted on read as `todo` and rewritten on the next touch. A task section has its own
466+state *and* the derived count of its subtree. See §8.
376467
377468### 4.6 Links
378469
@@ -384,39 +475,17 @@ External links are ordinary Markdown `[text](url)` and are opened with `xdg-open
384475
385476### 4.7 Embeds
386477
387−An embed is where a block is stitched into the tree. It has two forms, one per position in
388−the ordering rule (§3.1):
389−
390−- **bare**, `![[id]]` alone on a line at the node's indent — an item position;
391−- **heading**, `## ![[id]]` — a heading line holding nothing but the embed, at the level and
392− indent a section has at that position.
478+An embed is where a block is stitched into the tree: `![[id]]` alone on a line, at the
479+indent the node has at that position.
393480
394481```
395482![[dozzod-binwes-talsun-worbec]]
396483 ![[racfer-hattes-mislup-nodrys]] -- a child of the item above it
397−## ![[lacnum-walbyn-dirlyn-havtyp]] -- a section-position embed
398484```
399485
400−The app writes the form that matches the block's spelling: a block whose root is a section
401−is embedded with a heading embed, a block whose root is an item with a bare one. The
402−heading form exists because a bare embed after a section sibling would, like any line
403−there, belong to that section (§3.1). Either form is read anywhere; an embed whose form
404−does not match its block's spelling is a diagnostic (§15.7) that `fold check --fix`
405−rewrites, and it is placed where its form puts it.
406−
407−The embed names the block by id and nothing else; title, checkbox, properties, text and
408−children all live in the file. The embed's form repeats the block's spelling so that the
409−parent file parses into the right shape without opening the block. An embed line has no
410−children in the parent file; lines indented under a bare embed, or nested under a heading
411−embed, are a diagnostic.
412−
413−A heading embed's **level** is read like any heading's: honoured as written, and it
414−decides the embed's parent the same way. The level its position gives is `1 +` its section
415−ancestors (§3.1), and that is the level the app writes. A written level deeper than that —
416−left by another editor, or by moving text around by hand — still parses under the same
417−parent (a shallower one would parse elsewhere and is simply a different position), so
418−`fold check` reports it and `check --fix` rewrites it to the positional level; the
419−structure does not change.
486+The embed names the block by id and nothing else; title, spelling, checkbox or `todo`,
487+properties, body and children all live in the file. An embed line has no children in the
488+parent file; child lines indented under an embed are a diagnostic.
420489
421490`![[` anywhere else is body text. The app does not implement general transclusion.
422491
@@ -430,26 +499,20 @@ for a later query language (§17); leave such blocks alone and they will start w
430499A block's file is exactly `render(node, 1, resolve_blocks = false)`:
431500
432501```
433−[frontmatter, always beginning with id; done: if the block is a checked task]
502+[frontmatter, always beginning with id; todo: if the block is a task]
434503[the node's title line, as spelled: "# Title" or "- Title"]
435−[its children: text and nodes, in order]
504+[body]
505+[children]
436506```
437507
438508- The root is written as it is spelled in the tree. A block that is a heading is a `#` at column 0; one
439509 that is a bullet is a `- ` at column 0, with its body and children indented under it exactly
440510 as they would be in the parent. The file is the only place the spelling is recorded.
441−- The block's task state is the checkbox on its title line, with a `done:` date in the
442− frontmatter when it is checked. Toggling the node — from the outline, or the embed —
443− writes to the file.
444−- The first node at column 0 is the block's root. **Anything after it at column 0 — more
445− nodes, or text — is adopted as the root's children**, in order, as if it were indented
446− under it. That is what a phone editor produces by appending `- new thing` to a block
447− spelled as a bullet, and it must never make the block unusable. Adopted lines are
448− non-canonical (`fold check` notes them); the next write of the block, or `check --fix`,
449− writes them in place under the root (indented under a bullet root, re-levelled under a
450− heading root).
451−- Text before the root other than frontmatter, or an embed before it, is a diagnostic; the
452− app treats that file as read-only until fixed, since it cannot tell what the block is.
511+- The block's task state is `todo:` in its frontmatter, with a `done:` date beside it
512+ when done; there is no checkbox in the file. Toggling the node — from the outline,
513+ or the embed — writes to the file.
514+- A file with text before the root other than frontmatter, or with more than one node at
515+ column 0, is a diagnostic; the app treats the file as read-only until fixed.
453516- `root.md` is the file of the implicit Root node; its frontmatter holds vault-level
454517 properties (rarely needed) and never an `id`.
455518
@@ -464,7 +527,7 @@ Two boxes in the closet, one at Hetzner.
464527
465528## NAS
466529
467−### ![[dozzod-binwes-talsun-worbec]]
530+![[dozzod-binwes-talsun-worbec]]
468531
469532## Networking
470533
@@ -505,18 +568,17 @@ Mirrored pairs, no raidz. Snapshots hourly via sanoid.
505568```markdown
506569---
507570id: racfer-hattes-mislup-nodrys
571+todo: open
508572due: 2026-09-20
509573---
510574
511−- [ ] Order new switch
575+- Order new switch
512576 Two options, noted under Networking.
513577```
514578
515−Note that "Replace the flaky switch" is a one-line task with no file; "ZFS layout" is a
516−section block, so its embed is a heading at the level it has under NAS, while "Order new
517−switch" is an item block and is embedded bare. "Order new switch" became a block the moment
518−it got a due date; its checkbox moved into the file with its title line, and its file starts
519−with a bullet because that is what it is. "Options" is
579+Note that "Replace the flaky switch" is a one-line task with no file; "Order new switch"
580+became a block the moment it got a due date, its checkbox became `todo: open`, and its
581+file starts with a bullet because that is what it is. "Options" is
520582a section nested under an item, and "Snapshot policy" is a section that is itself a task.
521583
522584---
@@ -530,11 +592,10 @@ Emits the node and its subtree, in document order, as a standalone Markdown docu
5305921. If the node is a block and `base == 1` and `resolve_blocks = false`: its frontmatter.
5315932. The node's title line — checkbox included — at level `base` for a section, or at
532594 indent 0 for an item.
533−3. The node's children in order: each text child verbatim (dedented by the node's original
534− indent), each child node recursively — sections re-levelled by `base − level(node)`,
535− everything re-indented by `−indent(node)`.
536−4. Blank lines are text and are reproduced where they are (§3.3); `render` adds none.
537−5. Embeds: with `resolve_blocks = false`, written as embeds in their form (§4.7). With `resolve_blocks = true`, the
595+3. The node's body, verbatim (bodies dedented by the node's original indent).
596+4. Each descendant, recursively: sections re-levelled by `base − level(node)`; everything
597+ re-indented by `−indent(node)`.
598+5. Embeds: with `resolve_blocks = false`, written as embeds. With `resolve_blocks = true`, the
538599 block's subtree is inlined at the embed's position, its frontmatter omitted, its own
539600 embeds resolved in turn. The resolved text contains no embed, no id, no frontmatter and no
540601 file boundary: it is titles, checkboxes, bodies and nesting, nothing else.
@@ -567,8 +628,7 @@ A block is **dirty** when any line it owns changed, or when the position of a bl
567628in it moved. `splice(block)` writes one dirty block:
568629
5696301. Collect the block's owned lines in buffer order, and put each nested block back as an
570− embed at the position of that block's title line — a heading embed if that title line is
571− a heading, a bare one if it is a bullet (§4.7). The result is exactly an edited
631+ embed at the position of that block's title line. The result is exactly an edited
572632 `render(block, 1, false)`: one file's text, embeds intact, ids never typed.
5736332. Parse it: title lines, bodies, nesting, embeds. Exactly one root-level node; a `---`
574634 line is body text.
@@ -583,14 +643,13 @@ in it moved. `splice(block)` writes one dirty block:
583643
584644Splice runs without being asked (§10.6): when the cursor moves from a dirty block into
585645another block, after a pause in typing, on leaving the editor, before any outline verb,
586−before a reload, and on quit or any other end of the app. A commit may write several
587−files — one per dirty block — but each block is written by its own splice, and the law
588−holds per block.
646+before a reload, and on quit. A commit may write several files — one per dirty block — but
647+each block is written by its own splice, and the law holds per block.
589648
590649Edge cases follow from the tags, not from rules:
591650
592651- Renaming a nested block is editing its title line; it is that block's line, so its file
593− is rewritten (and keeps its name, §6.4). No second step.
652+ is rewritten and renamed (§6.4). No second step.
594653- Deleting a nested block's title line deletes the block: the file goes to trash, and any
595654 lines it still owned are re-tagged to the enclosing block — they become plain text of the
596655 parent, which is what deleting a heading does to its content in any editor. This is the
@@ -603,7 +662,7 @@ Edge cases follow from the tags, not from rules:
603662### 5.3 Levels
604663
605664Heading levels are unbounded (§3.1), so splice never changes a node's spelling and never
606−demotes anything.
665+demotes anything. The level-overflow rule of earlier drafts is gone.
607666
608667### 5.4 One function, three uses
609668
@@ -624,10 +683,8 @@ demotes anything.
6246831. Generate an id and a name (§6.4). The file is `<prefix>~<name>.md` in the vault root.
6256842. Write `render(node, 1, resolve_blocks = false)` to the file atomically, with frontmatter
626685 `id: <id>` and, if the block was made by setting a property, that property.
627−3. Replace the node's span in its parent file with an embed at the node's position: a
628− heading embed at its level for a section, a bare `![[id]]` at its indent for an item
629− (§4.7). The text children around the node stay in the parent. A checkbox on the node
630− stays on its title line, now in the block's file (§4.5).
686+3. Replace the node's span in its parent file with an embed, `![[id]]`, at the node's
687+ indent. A checkbox on the node becomes `todo: <state>` in the frontmatter (§4.5).
6316884. Update the index.
632689
633690There is no inverse. A block stays a file until it is deleted (`d`, *clear done*, or
@@ -642,8 +699,7 @@ or on `s` / *make block*. There is no other way.
642699
643700- Every block is embedded from **exactly one** embed. A second embed for the same id is
644701 a diagnostic and renders as broken. Cycles are a diagnostic.
645−- A block file has one root, its first node at column 0; later column-0 lines are adopted
646− under it (§4.9). Its frontmatter has a valid
702+- A block file has exactly one node at column 0 (§4.9) and its frontmatter has a valid
647703 `id`.
648704- Blocks may be nested: a block's file may itself contain embeds.
649705- Embed lines have no children in the parent file.
@@ -669,7 +725,7 @@ syllables (vowels `e u y`) — each word `prefix suffix`, words joined by `-`:
669725`racfer-hattes-dozzod-binwes`. No `~` sigil and no scrambling step, since the bits are random
670726rather than sequential. Never derived from content or time. Stored as the `id` frontmatter
671727key and validated against the syllable tables on read; a file whose `id` does not validate
672−is ignored, and `fold check` says why.
728+is ignored, and `notes check` says why.
673729
674730Collisions: 2⁶⁴ values, so two ids minted independently are never expected to collide; the
675731index still checks and redraws locally, and the merge engine still reports an id collision
@@ -696,18 +752,15 @@ splits without knowing how many words the prefix has. Because the prefix alone i
696752can never collide with anything, and `racfer~` already tells you which file you are looking
697753at before you read the rest. Cross-device collisions are found at merge time and resolved
698754the same way (§12.2). The filename carries no identity: a file is parsed because of its
699−`id` key, not its name, so `python-basics.md` with an `id:` is a block (and `fold
755+`id` key, not its name, so `python-basics.md` with an `id:` is a block (and `notes
700756check` asks for the prefix) while `racfer~order-new-switch.md` without one is ignored.
701757Safe on APFS, ext4, Android storage and Windows; case-insensitive-safe because lowercase.
702758
703−**Names are set once.** The name is the slug of the title when the block is made, and a
704−later title change does not rename the file. Under Syncthing a rename arrives on other
705−devices as a delete plus a create; a phone that edits the old name meanwhile turns that
706−into a sync conflict or an orphan, for nothing — the prefix alone identifies the file and
707−nothing references the filename. A file renamed or titled by another tool is still found by
708−its id. `fold check` reports filenames whose prefix is not a leading run of the id (a
709−problem) and names that no longer match the title (stale, harmless); `fold check --fix`
710−renames both, which is the one moment the app renames a file.
759+Renaming a block's title renames the file to the new name (temp-and-rename). Nothing
760+else in the vault references the filename, so nothing else is rewritten. A file renamed or
761+titled by another tool is still found by its id; `notes check` reports filenames whose
762+prefix is not a leading run of the id or whose name no longer matches the title, and
763+`notes check --fix` fixes them.
711764
712765### 6.5 Refile
713766
@@ -716,12 +769,6 @@ the re-levelled text at the destination (§5). Refiling a block moves only its e
716769the file does not move. Any node may be refiled under any node;
717770spelling is preserved. A task block's state travels with its file, not its embed.
718771
719−The subtree becomes the destination's **last child, clamped** by the ordering rule (§3.1): a
720−section goes after everything; an item goes after the destination's last item or text child,
721−just before its first section child. `Ctrl-Enter` (first child) is clamped the same way: a
722−section as first child lands just after the destination's last item. Text children of the
723−source's old parent stay behind.
724−
725772**Archive.** Archiving is refile with a fixed destination: the subtree is appended as the
726773last child of the top-level section titled `Archive` (case-insensitive, like `Inbox`;
727774created as the last top-level section of `root.md` if missing). Nothing else happens — no
@@ -735,13 +782,11 @@ delete it. Nothing in the archive is hidden or dimmed.
735782
736783- The inbox is the top-level section titled `Inbox` (case-insensitive). If none exists,
737784 capture creates `# Inbox` as the last top-level section of `root.md`. If several exist,
738− the first in `root.md` order wins and `fold check` reports the rest.
739−- **Capture** (`c` in the TUI, `fold capture`, a global hotkey via the CLI) appends an item
785+ the first in `root.md` order wins and `notes check` reports the rest.
786+- **Capture** (`c` in the TUI, `notes capture`, a global hotkey via the CLI) appends an item
740787 under today's day: the child section of `Inbox` titled with today's date, `YYYY-MM-DD`,
741788 created on demand as the last child. `--task` or a leading `[ ]` makes it a task.
742− `--to <target>` captures under any node instead. A captured item is clamped like any
743− inserted item (§3.1): under a target with section children it lands just before the first
744− of them. Capture never creates a block; dates
789+ `--to <target>` captures under any node instead. Capture never creates a block; dates
745790 and properties are set in the TUI afterwards.
746791- Days are ordinary sections, appended in order, so the inbox reads oldest to newest. They
747792 can hold prose, tasks and sub-sections like any section, and `2026-09-11` addresses
@@ -760,7 +805,7 @@ delete it. Nothing in the archive is hidden or dimmed.
760805| Node | open | done |
761806|---|---|---|
762807| not a block | `- [ ] Title` / `## [ ] Title` | `[x]` |
763−| block | `- [ ] Title` / `## [ ] Title` | `[x]` + `done: <date>` in frontmatter |
808+| block | `todo: open` | `todo: done` + `done: <date>` |
764809
765810Any node can be a task. A checkbox task has exactly this much state and nothing else. Its
766811completion date is not recorded; if you need one, make the task a block so toggling
@@ -769,9 +814,9 @@ writes `done:` (§8.2).
769814### 8.2 Task blocks
770815
771816A task that needs a date, a property, a completion record, or a body with structure becomes
772−a block (§6.1). Its checkbox moves into the block's file with its title line; the embed in
773−the parent is `![[id]]`. Toggling from anywhere (outline or embed) rewrites that checkbox and
774−stamps or removes `done:`.
817+a block (§6.1). Its checkbox becomes `todo:` in the frontmatter; the embed in the parent
818+is `![[id]]` and the title line in the file is plain. Toggling from anywhere
819+(outline or embed) rewrites `todo:` and stamps or removes `done:`.
775820The reading pane renders a resolved task embed on one line:
776821
777822```
@@ -798,12 +843,10 @@ Dates are ISO, `YYYY-MM-DD`. The property editor refuses anything else for `due`
798843
799844Done items stay where they are: one line each, in the context that gave them
800845meaning, which is the cheapest history there is. The panes dim them, and `zd` toggles
801−hiding them (remembered with the view, §10.1). The selection stays on its node; one
802−hidden with them gives way to its next shown sibling, else the row above it.
846+hiding them for the session.
803847
804848When a list is genuinely finished, *clear done* (palette) trashes every done item with no
805−open descendants under `target` (default: current zoom root), unless a conflict copy of
806−it stays (§12.5); task blocks'
849+open descendants under `target` (default: current zoom root); task blocks'
807850files go to trash too. For whole finished subtrees — a completed project, a concluded
808851meeting series — `za` refiles them under `Archive` (§6.5). The trash (§11.5) and file versioning (§12.6) backstop both.
809852
@@ -816,162 +859,46 @@ Out of scope for 1.0. Nothing is reserved for it.
816859## 9. Query language
817860
818861Not in 1.0. Search is the filter box (§10.5): fuzzy title match plus full-text match over
819−the whole vault, live, no syntax. A query language is future work (§17, §19 decision 16); the
820−```` ```query ```` fence is reserved for it (§4.8).
862+the whole vault, live, no syntax. A query language with saved views was designed and set
863+aside (§17, open decision 16); the ```` ```query ```` fence is reserved for it (§4.8).
821864
822865---
823866
824867## 10. TUI
825868
826−### 10.1 Layout and the pointer
869+### 10.1 Layout
827870
828871```
829− fold › Homelab › NAS ⌕ Filter + Capture ↶ Undo ↷ Redo ☰ Commands ? Help
830−╭ Outline ───────────────────────╮╭ ZFS layout ▤ ─────────────────────────── ✎ Edit ─ ⋯ ─╮
831−│▾ Homelab 2/4 ││ # ZFS layout │
832−│ ▾ NAS 1/2 ││ ⚑ since 2024-03 │
833−│ ▾ ZFS layout ▤ 1/2 ⋯││ │
834−│ ▾ ☐ Snapshot policy ││ Mirrored pairs, no raidz. Snapshots hourly. │
835−│ ▸ Networking 1/2 ││ ## ☐ Snapshot policy │
836−│▾ Inbox ││ - hourly, keep 24 │
837−╰────────────────────────────────╯╰───────────────────────────────────────────────────────╯
838− moved “Label the cables” ⚠ 1 conflict root.md ✓ saved
872+┌ Homelab › NAS › ZFS layout ────────────────────────── 3/7 open ┐
873+│ outline pane (titles only) │ reading pane (zoomed) │
874+│ ▾ Homelab 2/9 │ # ZFS layout │
875+│ ▾ NAS │ since 2024-03 · storage │
876+│ ▸ ZFS layout ▤ 1/2 ◀ │ │
877+│ Networking 1/2 │ Mirrored pairs, no raidz…│
878+│ ▾ Inbox │ │
879+│ 2026-09-10 1/2 │ ## Snapshot policy │
880+│ │ - hourly, keep 24 │
881+│ │ ☑ Move scratch to its… │
882+├─────────────────────────────────────┴──────────────────────────┤
883+│ : root.md · saved · 12:04 │
884+└────────────────────────────────────────────────────────────────┘
839885```
840886
841−Four regions, each of them live under the pointer:
842−
843−- The **top bar**: the breadcrumb of the zoom root (`fold` is the vault root; every
844− segment is a link that zooms there), and buttons for *Filter*, *Capture*, *Reading
845− pane*, *Undo*, *Redo*, *Commands* and *Help*. Buttons shrink to their icons when the bar
846− is narrow; a toggle's button looks pressed while it is on.
847−- The **outline pane**: titles, fold markers (`▸ ▾`), task glyphs (`☐ ☑`), the block
848− marker (`▤`), a due date and the open/total count of the tasks below (a leaf shows no
849− count). Done tasks are dimmed and struck through; sections are bold. Long titles end in
850− `…`. Beside the reading pane, the date and count sit at the pane's right edge and rows
851− show titles only. Without it, they follow the title, and after them, dimmed, comes the
852− first line of the node's own text, so no prose is out of sight. A conflict copy
853− (§12.5) has `⚠`, in the warning colour, for `▤`, and, in place of its text, whose copy
854− it is: *other device · PHONE 09-27 10:00*. It starts folded until unfolded or zoomed
855− into, a fold the view does not remember.
856−- The **reading pane**: `render(target, 1, true)`, the zoom root's or else the selected
857− node's, with light Markdown styling (§10.9); a block's properties appear as a dimmed
858− `⚑` line under its title, never as text, and a conflict copy's title line ends in its
859− `⚠` and whose copy it is, as its row does. Its border carries the node's title and the
860− buttons *Edit* and `⋯` (the node menu). While editing, the pane is the editor (§10.6) and
861− its buttons are *Done* and *Revert*.
862−- The **status bar**: the last message on the left (at start, a hint at the gestures and
863− `?`, cut down to *right-click for actions · ? help* where it doesn't fit); on the right
864− the unresolved conflicts (click to resolve; lit while new ones wait, §10.7), *done
865− hidden* when `zd` is on (click to show), the file, and the save state. A verb's message
866− names the node it acted on, in the menus' words, with the next key where one helps —
867− *deleted “Homelab” (12 nodes) · u undoes*, *moved “rack” to “NAS” — placed before the
868− sections* when the ordering rule chose the place (§3.1); in the reading pane, whose keys
869− are its own (§10.4), the step is the menu of the line's node: *copied “NAS” · m, then
870− choose Paste after*; in the editor, where every key is text (§10.6), it is the
871− pointer's, short enough to keep the node and the item to choose at 80 columns: *copied
872− “NAS” · right-click, Paste after*, *deleted “rack” · click ↶ Undo* — and names no file
873− or id (§1, 3b), but for undo or redo refusing
874− (§10.10); where the bar is short, the right gives way first, the ⚠ count down to its
875− number (*⚠ 1*), then the file, until the message fits; then the names are cut down,
876− the longest first, before the words around them. Once a message is some 5 s old and a key
877− or click has come since, the next step takes its place: the keys of what is on screen —
878− *n new · e edit · x done · m menu · / find · ? help* in the outline, *e edit · Enter
879− zoom/follow · Tab outline* in the reading pane, *Esc done · Ctrl-S save · Ctrl-Z undo* in
880− the editor (*i insert · :q! revert · :wq done* in Vim and Helix, where `Esc` never
881− leaves), *n add · Enter change · d delete · Esc close* in the property form, and each
882− popup's own — cut down by whole parts where they don't fit, the first and the last kept,
883− so the way on (*? help*, *Esc done*, *:wq done*) stays. An error or a refusal
884− (*error: …*, *can't …*, *… refused: …*) stays until a key or click that comes once it
885− is that old. The greeting is the outline's, and gives way at once to the editor's or a
886− popup's keys. While a key sequence is half typed, the bar says what can follow it:
887− *z… p pane · w wrap · d hide done · r raw · a archive*, cut down from its end where it
888− doesn't fit; with done hidden, *d show done* comes first.
889−
890−The outline fills the screen. The reading pane is hidden until asked for: `zp` or the top
891−bar's `◨` (*Reading pane*) shows it, `Tab` shows it and moves focus there, and the editor
892−(§10.6) always opens in it, hiding it again when done if it was hidden before. Shown, the
893−layout splits: outline on the left at a third of the width (minimum 30 columns); the border
894−between the panes is a handle — drag it to resize. Below 80 columns the panes stack.
895−
896−**The view is remembered** per vault, in `$XDG_STATE_HOME/fold/views/`: whether the
897−reading pane is shown, wrapping, hidden done tasks, the editor's keymap, the divider's
898−place, the folds and the zoom. None of it is in the vault; another device keeps its own.
899−A fold or zoom whose node has gone is dropped.
900−
901−**Long lines wrap** in the reading pane and the editor. Prose breaks after a space, and a
902−wrapped list item's continuation rows hang under its text, not under its bullet or
903−checkbox; a word longer than the pane breaks where it must. Lines inside fenced code break
904−at the pane's edge instead, one column early, with `↪` marking each break, so code is
905−never reflowed. Every screen row of a line is that line to the pointer: a click on any
906−row selects it or places the cursor where it lands. `zw` (*Wrap lines*) turns wrapping
907−off, and long lines are cut at the edge. The outline never wraps: a long
908−title ends in `…`.
909−
910−**The pointer.** Everything a key does, the pointer does too, and the screen shows where:
911−
912−| Gesture | Where | Does |
913−|---|---|---|
914−| click | an outline row | select it (the reading pane follows) |
915−| click | `▸` / `▾` | fold / unfold |
916−| click | `☐` / `☑`, in either pane | toggle the task |
917−| click | a conflict copy's `⚠`, in either pane | the conflict view at its pair (§10.7) |
918−| click | `⋯` (shown on the selected and the hovered row) | the node menu |
919−| click | a breadcrumb segment | zoom there |
920−| click | a link in the reading pane | open it |
921−| click | a line in the reading pane / the editor | put the cursor there |
922−| double-click | an outline row | zoom into it |
923−| double-click | a line in the reading pane | a heading: zoom into it; an embedded block: follow it; anything else: open the editor with the cursor on that line |
924−| right-click | a row or a line | the node menu for that node |
925−| drag | an outline row onto another row's **title** | move it **into** that node, as its last child |
926−| drag | an outline row onto the space **left of** another row's title | move it **before** that node, as its sibling |
927−| wheel | either pane, the editor, any list | scroll what is under the pointer; the selection stays put |
928−| drag | the border between the panes | resize them |
929−| click | outside any popup | close it |
930−
931−While dragging, the target row is highlighted (*into*) or marked with `▶` and a bar
932−(*before*), the row being moved is dimmed, the status bar spells out the move, and the pane
933−scrolls when the pointer reaches its edge. Both drops are clamped by the ordering rule
934−(§3.1) and say so when they are; a drop into the node's own subtree is refused, and so is
935−one into a conflict copy it is not in, which keeping ours trashes (§12.5): over the copy's
936−title or a row in it, nothing is marked and the status bar says why. One drag is one undo
937−step.
938−
939−**No key acts out of sight.** The wheel leaves the selection, and the reading pane's cursor,
940−where they are, even out of view. A key that acts on one — in the outline
941−`x t d s J K > < ~ za r p P`, `e a m y` and the folds; in the reading pane `x`, `Enter` and
942−`e a m o` — first scrolls it back into view, a third of the way down. A key that changes
943−nothing then acts; one that changes something stops there, and until the next key or click
944−the status bar says what a second press does: *press d again to delete “Replace fan”*.
945−Pressed again, with the node in view, it acts. The pointer acts where it points and is
946−never held back.
947−
948−**The node menu** (right-click, `⋯`, or `m`) lists every action on a node, each with its key:
949−*Edit · Zoom in · Properties… | New sibling · New child | Done / reopen · Task on / off ·
950−Heading ↔ bullet · Make block | Move up · Move down · Indent · Outdent · Move to… · Archive |
951−Copy · Paste after · Paste before · Delete*, and on either side of a conflict pair, last,
952−*Resolve conflict…*: the conflict view at that pair. Moving the pointer onto an item
953−highlights it, and a click runs it; `↑`/`↓` and the wheel move the highlight on, whatever
954−item the pointer rests on. On a screen too short for it the separators go first, then the
955−list scrolls with the selection.
956−
957−**Pickers instead of typing.** Where an action needs a node — *Move to…*, *Go to…* — the
958−prompt is a list of every node, title first and path dimmed, narrowed as you type (title
959−prefix, then title, then path) and picked with a click or `↑`/`↓` and `Enter`. A typed id,
960−path or title (§3.4) still works. *Move to…* leaves out the moving node's own subtree, and
961−its conflict pair's, which moves with it, and conflict copies, which keeping ours trashes
962−(§12.5); *Go to…* marks a node in one `⚠`.
963−
964−Every popup — node menu, prompt, properties, filter, commands, help — has its buttons on
965−its bottom border (*OK*, *Close*, …), a list you can click and scroll, and closes on a click
966−outside it or `Esc`. It covers the panes: a click, right-click or wheel inside it never
967−reaches what is behind it, so a right-click there opens no node menu.
887+- The **outline pane** shows titles, fold state, task glyphs, derived counts, a block
888+ marker (`▤`) and, for blocks, `due` dimmed. Never bodies.
889+- The **reading pane** shows `render(cursor, 1, true)` with light Markdown styling; a
890+ block's properties appear as a dimmed header under its title, computed from the
891+ index, never as text. `zr` (session toggle) shows the text unstyled.
892+- The breadcrumb shows ancestors of the zoom root; the status line shows file, save state,
893+ pending conflicts, and the last message.
894+- Split layout, outline on the left at a third of the width (minimum 30 columns); `Tab`
895+ cycles focus. Below 80 columns the panes stack and `Tab` switches between them.
968896
969897### 10.2 Modes
970898
971899`normal` (default), `edit` (the built-in editor over a subtree's Markdown, saving as you go), `filter`
972−(`/` box), `picker` (the command palette), `properties` (the property form), `help`,
973−`conflict` (§12.5). The node menu and prompts open over any mode. `Esc` — or a click outside
974−the popup — closes the topmost thing.
900+(`/` box), `picker` (fuzzy lists, the palette included), `conflict` (§12.5).
901+`Esc` always returns to normal.
975902
976903### 10.3 Outline pane — normal mode
977904
@@ -983,8 +910,6 @@ the popup — closes the topmost thing.
983910| `H` / `L` | collapse / expand all under cursor |
984911| `zd` | toggle hiding done nodes (both panes) |
985912| `zr` | toggle raw mode: exact source in the reading pane, frontmatter included |
986−| `zw` | toggle wrapping of long lines in the reading pane and the editor (§10.1) |
987−| `zp` | show / hide the reading pane (§10.1) |
988913| `za` | archive: refile subtree under `Archive` (§6.5) |
989914| `-` | go to parent |
990915| `{` / `}` | previous / next sibling |
@@ -992,31 +917,24 @@ the popup — closes the topmost thing.
992917| `Ctrl-d` / `Ctrl-u` | half-page down / up |
993918| `Enter` | zoom: reading pane shows the cursor node; reading pane takes focus |
994919| `Backspace` | zoom out to parent |
995−| `>` / `<` | demote / promote: become the last child of the previous sibling node / the next sibling of the parent, both clamped by the ordering rule (§3.1): an item promoted out of a section lands just before the parent's first section sibling. Spelling unchanged. `>` is refused under a conflict copy, which keeping ours trashes; a node and its conflict copy go as one, from either side (§12.5) |
996−| `~` | toggle spelling, section ↔ item (subtree unchanged). The node moves to its parent's boundary (§3.1): an item respelled as a section becomes the first section child, a section respelled as an item the last item child, so no sibling changes parent. Refused on either side of a conflict pair until it is resolved (§12.5) |
997−| `J` / `K` | move node down / up past the next / previous sibling node; text children stay put. A node and its conflict copy (§12.4) move as one, and a node moves past them as one. An item never moves below a section sibling, nor a section above an item: the move is refused with a message |
998−| `n` / `N` | new sibling after cursor / new last child, spelled like the cursor node / like the last child node (a section if the parent has section children): inserts an empty node and opens it with `e` |
920+| `>` / `<` | demote / promote: become the last child of the previous sibling / the next sibling of the parent. Spelling unchanged |
921+| `~` | toggle spelling, section ↔ item (subtree unchanged) |
922+| `J` / `K` | move node down / up among siblings |
923+| `n` / `N` | new sibling after cursor / new last child: inserts an empty node and opens it with `e` |
999924| `e` | edit the subtree's Markdown in the built-in editor; saves as you go (§10.6) |
1000925| `a` | property editor: a form over the node's properties (§10.6); first property on a plain node makes it a block |
1001−| `x` | toggle task open / done: the checkbox, plus `done:` on a block |
1002−| `t` | toggle task-ness: adds or removes the checkbox (and a block's `done:`) |
926+| `x` | toggle task open / done (checkbox, or `todo:` plus `done:` on a block) |
927+| `t` | toggle task-ness: adds or removes the checkbox, or the `todo` key on a block |
1003928| `s` | make the node a block |
1004−| `y` / `d` | yank / delete subtree into the register (delete goes to trash too, and takes the node's conflict copies with it, §12.5) |
1005−| `p` / `P` | paste register after / before cursor as sibling, clamped by the ordering rule (§3.1) |
929+| `y` / `d` | yank / delete subtree into the register (delete goes to trash too) |
930+| `p` / `P` | paste register after / before cursor as sibling |
1006931| `r` | refile: fuzzy-pick a destination; `Ctrl-Enter` = as first child |
1007932| `c` / `C` | capture to the inbox as bullet / as task |
1008933| `/` | filter box (§10.5) |
1009−| `m` | the node menu (§10.1) |
1010−| `:` | command palette (§10.8); `?` or `F1` help |
934+| `:` | command palette (§10.9); `?` help |
1011935| `u` / `U` | undo / redo |
1012936| `q` | quit (nothing is ever unsaved in normal mode) |
1013937
1014−A second key that follows the first in no sequence (`zq`) does nothing and says so: *zq
1015−does nothing · after z press p, w, d, r or a*; `Esc` or a click lets the first key go. A
1016−few keys fold has no use for, where one often reaches for them, say what to press instead:
1017−`i` (*i does nothing here: e edits*), `o` (*n adds a node below*), `Delete` (*d deletes*),
1018−`Ctrl-Z` (*u undoes*), `Ctrl-F` (*/ finds*); any other key that does nothing says nothing.
1019−
1020938### 10.4 Reading pane — normal mode
1021939
1022940| Key | Action |
@@ -1029,20 +947,17 @@ few keys fold has no use for, where one often reaches for them, say what to pres
1029947| `o` | open external link under cursor (`xdg-open` / `open`) |
1030948| `[[` / `]]` | previous / next heading |
1031949| `/` | in-block search; `n` / `N` next / previous match |
1032−| `:` `?` `F1` `m` `c` `C` `u` `U` `z…` | as in the outline; `m` is the menu of the node under the cursor; `i`, `Delete` (*m, then choose Delete*), `Ctrl-Z` and `Ctrl-F` (*/ searches*) say what to press instead |
1033950
1034951### 10.5 Filter box
1035952
1036−`/`, or *Filter* in the top bar, opens a popup with an input and the hits below it:
1037−fuzzy title match (the typed letters in order) and full-text match over every text child, each hit shown as its
1038−title with its path dimmed, and `⚠` after the title of a hit in a conflict copy (§12.5).
1039−Clicking a hit — or `↑`/`↓` and `Enter` — unfolds its ancestors and selects it. `Esc` or a
1040−click outside closes. Creating nodes is `n` / `N` (§10.3).
953+`/` opens a single input over the outline. Typing filters the outline live by fuzzy title
954+match (nucleo) and, after a 150 ms pause, by full-text match, showing ancestors of hits.
955+`Enter` on a highlighted hit zooms to it; `Enter` with no hit does nothing. `Esc` clears.
956+Creating nodes is `n` / `N` (§10.3).
1041957
1042958### 10.6 Editing
1043959
1044−There is one way to change text: edit the Markdown of the selected subtree. `e`, *Edit* on
1045−the reading pane or in the node menu, or a double-click on a line of text shows
960+There is one way to change text: edit the Markdown of the selected subtree. `e` shows
1046961`render(cursor, 1, true)` — the node's title line, body and children, with every
1047962nested block inlined so that no embed, id, frontmatter or file boundary appears.
1048963
@@ -1053,81 +968,16 @@ app keeps track. Blocks are saved on their own:
1053968
1054969- when the cursor moves out of a block that has changed;
1055970- after 750 ms without a keystroke;
1056−- on `Esc` or *Done* (back to normal mode), on any outline verb, before a reload caused by
1057− an external change, and on quit;
1058−- when the app ends any other way: `SIGTERM`, `SIGHUP` (a closed window, a dropped ssh
1059− session), `SIGINT`, an I/O error, a panic. A second `SIGTERM` or `SIGINT` ends it at
1060− once, unsaved, as the signal would: the way out while a write waits on a terminal that
1061− reads nothing. A `SIGHUP` before one does not make it a second.
1062−
1063−An outline verb that writes re-renders the editor over what it wrote, so the save before it
1064−deletes a block cut there and not pasted back (§5.2). One that writes nothing — a copy, a
1065−zoom, help, the palette, the filter, a view toggle, or a prompt or the property form until
1066−it writes — leaves the editor as it is, a cut block still to paste.
1067−
1068−A save shows in the status bar's save state (§10.1), not as a message, but for `:w` and
1069−`Ctrl-S`, which are asked for and answer *saved*, and one that an outside change forced,
1070−which the news of what came in mentions first, so that where the bar is short the list
1071−of it gives way: *↻ typing saved; changed outside fold: Inbox (+1 item)* (§11.2). News
1072−of a sync conflict says what to do and no more (§10.7).
1073−
1074−The pane's border shows the title of the block the cursor is in, *⚠ conflict copy from
1075−PHONE* after it in a conflict copy (§12.5), and a dot while something is unsaved, and lines
1076−of other blocks in the subtree are drawn a shade dimmer — the only hints that blocks
1077−exist. The border's text stays left of `⌨` and the buttons: where that is short, the title
1078−gives way first, to its first letters, then the words after ⚠ (*⚠ copy from PHONE*, then
1079−*⚠*), then *Editing*; the ⚠, the dot and the Vim or Helix mode stay whole.
1080−*Revert* (or `:q!`) discards changes made since the last save.
1081−
1082−Text a save cannot take — its block changed on disk under it (§5.2) — stays in the editor,
1083−and is never dropped without a copy unless asked twice: *Revert* first writes the editor's
1084−whole text to the trash as `unsaved-<title>.md` (§11.5) and the status line names the entry,
1085−and the app ending any way but a quit does the same, saying where once the terminal is back:
1086−a panic in the save itself too, whether an autosave, `Esc` or leaving a block ran it.
1087−Where the trash cannot be written, *Revert* drops nothing and says so, and a second *Revert*
1088−on the same text drops it. However it ends, but for that second signal, the app leaves the
1089−terminal as it found it, a panic's message printed after.
1090−
1091−**Keymaps.** The editor speaks one of three keymaps, over the same text, cursor, selection,
1092−clipboard and undo:
1093−
1094−- **normal** (the default): a conventional editor in the manner of micro. Always typing;
1095− arrows, `Home`/`End`, `PgUp`/`PgDn`, `Ctrl-←/→` by word; `Shift` with any of them
1096− selects; typing replaces the selection. `Ctrl-C`/`X`/`V` copy, cut, paste (with nothing
1097− selected, the current line); `Ctrl-Z`/`Y` undo, redo; `Ctrl-A` select all; `Ctrl-K` cut
1098− the line; `Ctrl-D` duplicate; `Alt-↑/↓` move lines; `Tab`/`Shift-Tab` indent;
1099− `Ctrl-F` find, `Ctrl-N`/`Ctrl-P` next, previous; `Ctrl-S` save; `Ctrl-E` a `:` command;
1100− `Esc` clears the selection, then leaves.
1101−- **vim**: normal, insert, visual and visual-line modes; counts; motions `h j k l w b e W
1102− B E 0 ^ $ gg G { } % f t F T ; ,`; operators `d c y > <` with any motion, doubled for
1103− lines, and text objects `iw aw i" a" i' a' i( a( i[ a[ i{ a{ i< a< ip ap`; `x X s S D C Y
1104− r J ~ p P o O i a I A u Ctrl-R .`; `/ ? n N * #`; `ZZ`, `ZQ`.
1105−- **helix**: selection first — `w b e W B E` select, `x` selects (and extends by) lines,
1106− `% ; Alt-;`, `f t F T`, `gg ge gh gl gs`, `mi` / `ma` + object, `v` to extend; `d c y p P
1107− R r ~ > < J` act on the selection; `i a I A o O`; `u U`; `/ ? n N *`.
1108−
1109−With wrapping on (§10.1), the normal keymap's `↑`/`↓`/`PgUp`/`PgDn` and Helix's `j`/`k`
1110−move by screen row through a wrapped line, as micro and Helix do; Vim's `j`/`k` move by line,
1111−and `gj`/`gk` by screen row.
1112−
1113−All three take `:` commands — `:w` save, `:q` / `:wq` / `:x` done, `:q!` / `:e!` revert,
1114−`:N` go to line — and in all three a click puts the cursor where it lands, a drag
1115−selects, a double-click selects a word, the wheel scrolls, and text pasted into the
1116−terminal is inserted as typed. Copying also sets the system clipboard (OSC 52). Undo
1117−inside the editor is the editor's own; each save is still one entry in the op log (§10.10).
1118−`F1` shows the help, `?` being text here.
1119−
1120−The keymap is `$FOLD_KEYS` (`normal`, `vim`, `helix`), or `--keys`; the *Editor keys*
1121−action, or the `⌨` label in the editor's border, switches it, and the choice is remembered
1122−with the view (§10.1) unless `--keys` or `$FOLD_KEYS` chose the keymap for this run. When
1123−editing ends, focus returns to the pane it came from. Undo inside the editor is the
1124−editor's own; each automatic save is one entry in the session op log (§10.10). The box is
1125−not a Markdown editor and stays one.
1126−
1127−Properties are not text. `a`, or *Properties…* in the node menu, opens the **property
1128−editor**: a small form listing the
1129−node's keys and values: click a value to change it (the prompt starts with the current
1130−value), `✕` deletes an entry, *Add* adds one. `due` and `done` accept
971+- on `Esc` (back to normal mode), on any outline verb, before a reload caused by an
972+ external change, and on quit.
973+
974+The status line shows the title of the block the cursor is in, dimmed, and a dot while
975+something is unsaved — the only two hints that blocks exist. `Ctrl-c` discards changes made
976+since the last save. Undo inside the editor is the editor's own; each automatic save is one
977+entry in the session op log (§10.11). The box is not a Markdown editor and stays one.
978+
979+Properties are not text. `a` opens the **property editor**: a small form listing the
980+node's keys and values, where you add, change and delete entries. `due` and `done` accept
1131981only ISO dates; `id` is not shown. Setting the first property on a node that is not a
1132982block makes it one (§6.1), silently except for the `▤` marker; deleting the last one leaves
1133983the block as it is. Frontmatter lines the app does not understand (nested structure,
@@ -1138,93 +988,47 @@ editing are the same gesture. `x`, `t`, `~`, `>`/`<`, `J`/`K`, `r` and `d` are t
1138988structural verbs; they exist because toggling a checkbox should not require the editor,
1139989and each saves the editor first.
1140990
1141−### 10.7 Conflict view
1142−
1143−Opened by the status bar's ⚠ count or *Resolve conflicts*; at a pair, by a copy's `⚠` or
1144−*Resolve conflict…* in either side's node menu; and on its own when a `.sync-conflict-*`
1145−file is detected or a splice hits a changed span — on the first new pair in the outline,
1146−and only from an outline at rest: normal mode, nothing open over it, no key, paste or
1147−click for 2 s. A busy user is not interrupted: the mode stays, the editor keeps its text,
1148−the status line names the node — *sync conflict in “NAS”: click ⚠ to resolve*
1149−— and the ⚠ count is lit until the view opens, from a click or on its own once the outline
1150−is at rest. Opened on its own, for its first half second it ignores `o t b e`, meant for
1151−what was there before. Pairs that come in while it is open leave it on the pair it shows.
1152−
1153−It shows each conflict pair (ours in place, the `conflict:` block right after it) side by side; `o` keeps ours,
1154−`t` keeps theirs, `b` keeps both, `e` edits, `n` / `N` next / previous, `u` / `U` undo / redo,
1155−`Enter` finishes. Each choice names its node — *kept theirs for “NAS” · u undoes* — and is
1156−one undo step.
1157−The view's top bar carries the same as buttons — *Previous*, *Next*, *Keep ours*, *Keep
1158−theirs*, *Keep both*, *Edit ours*, *Close* — and the two versions sit side by side, this
1159−device's on the left.
991+### 10.7 (removed)
992+
993+Result views went with the query language (§9).
994+
995+### 10.8 Conflict view
996+
997+Entered when a `.sync-conflict-*` file is detected or a splice hits a changed span. Shows
998+each conflict pair (ours in place, the `conflict:` block right after it) side by side; `o` keeps ours,
999+`t` keeps theirs, `b` keeps both, `e` edits, `n` / `N` next / previous, `Enter` finishes.
11601000Unresolved pairs remain as `conflict:` blocks and stay listed in the status
11611001line until resolved (§12.5).
11621002
1163−### 10.8 Command palette
1003+### 10.9 Command palette
11641004
1165−`:`, or *Commands* in the top bar, opens a popup listing every action the TUI has — key,
1166−name, what it does — filtered as you type: names that start with the query first, then
1167−names and descriptions that contain it, then fuzzy matches. A toggle shows its state
1168−beside its name (*Reading pane · on*). A click or `Enter` runs the highlighted action; if
1169−it needs an argument — a node for *Move to…* or *Go to…*, text for *Capture* — its prompt
1170−follows (§10.1). `Esc` or a click outside closes.
1005+`:` opens a popup listing every action the TUI has, filtered live by fuzzy match (nucleo)
1006+on the action's name and description, each row showing its key binding if it has one.
1007+`Enter` runs the highlighted action; if it needs an argument — a target for *refile* or
1008+*go to*, text for *capture* — a single prompt follows, with the same completion the
1009+key-bound form has. `Esc` closes.
11711010
11721011There is no command syntax: nothing is typed except the search and the argument. Every
1173−action is in the palette under a readable name, and the four that have no key or button
1174−live only there: *Clear done*, *Canonicalize*, *Merge sync conflicts*, *Resolve
1175−conflicts*. `?` is help: the pointer gestures first, then the keys. Where it is taller
1176−than the screen it scrolls, a bar on its right border: `j`/`k`, `↑`/`↓` or the wheel.
1177−
1178−### 10.9 Markdown styling in the reading pane
1179−
1180−Line-based, not a full renderer: headings coloured by level, task checkboxes shown as `☐ ☑`
1181−(clickable; done lines dimmed and struck through), links underlined (clickable),
1182−`` `code` `` tinted, `**bold**` and `*em*` styled, quotes in colour.
1183−
1184−**Fenced code** sits on a tinted background, its fence dimmed and its info string in the
1185−accent colour, and is **syntax-highlighted** with tree-sitter when the info string's first
1186−word names a known language. The app compiles in every tree-sitter grammar published on
1187−crates.io that builds against its tree-sitter version and has a highlights query — about
1188−140 languages, from Ada to Zig, including every mainstream programming, shell, markup,
1189−config, query and hardware-description language that has such a crate — keyed by the
1190−language's name and its usual fence aliases (`rs`, `py`, `js`, `ts`, `sh`, `c++`, `c#`,
1191−`kt`, `rb`, `hs`, `ex`, `yml`, `makefile`, `dockerfile`, `sql`, …). Where a grammar crate
1192−ships a highlights query without exporting it, the app carries a copy
1193−(`crates/fold-tui/queries/`, with its provenance). `rust,ignore`, `{.python}` and `sh
1194−title=x` work too. Each grammar is loaded on first use; the block is highlighted as a whole
1195−(so a string or comment spanning lines is right) and cached until its text changes. Any
1196−other language, or a block its grammar cannot parse, is shown in the
1197−plain code colour. Highlighting is the reading pane's only: the editor is a plain text box
1198−(§10.6). Raw text is
1199−never hidden: `#`, `-`, `**` and link targets stay on screen, dimmed. `zr` shows exact
1200−source, frontmatter included.
1201−
1202−### 10.10 Undo, redo, saving
1012+action reachable by key is in the palette under a readable name (*make block*,
1013+*toggle task*, *archive*, …), and the four with no key live only there: *clear done*,
1014+*canonicalize*, *check*, *merge*. `?` is the same popup filtered to show keys, so help and
1015+palette are one thing.
1016+
1017+### 10.10 Markdown styling in the reading pane
1018+
1019+Line-based, not a full renderer: headings by level, task glyphs (`☐ ☑`), resolved blocks'
1020+properties dimmed at the end of the line, underlined external links, styled code fences,
1021+`**bold**` / `*em*` / `` `code` `` inline, blockquote bars. Raw text is never hidden;
1022+`zr` shows exact source, frontmatter included.
1023+
1024+### 10.11 Undo, redo, saving
12031025
12041026Every mutation is an operation with an inverse (span edits, file create/move/delete,
12051027including making blocks). The session op log powers `u` / `U`. Outline verbs write
12061028immediately and atomically; the built-in editor writes each dirty block within 750 ms of the
12071029last keystroke, or sooner when the cursor leaves it (§10.6), so the only unsaved text at any
1208−moment is under a second of typing in one block.
1209−
1210−An op-log entry records **exactly the files the operation touched**: for each, its text
1211−before and after, where *absent* stands for a file the operation created or deleted (making
1212−a block, trashing one). An operation that changed nothing leaves no entry, so it does not
1213−clear the redo stack. Undo checks every touched file against its *after* text, and redo
1214−against its *before* text; if any differs — another editor or a sync changed it since — it
1215−refuses with a message naming the file, writes nothing, and keeps the entry. An entry is
1216−named in words, by what was done to which node — *mark “rack” done*, *delete “Homelab”*,
1217−*edit “NAS”* for an editor save — and undo and redo say it: *undone: mark “rack” done*,
1218−*undo refused: root.md changed since mark “rack” done; not overwriting*. Files the
1219−operation did not touch are never written by undo, so an external edit to them survives.
1220−A block cut in the editor and not pasted back (§5.2) is trashed in the entry of the save
1221−that wrote its embed out, even when a later save, *Revert* or a change from outside that
1222−re-renders the editor (§11.2) is what deletes it, so one undo puts back its embed and its
1223−file together, or refuses both where that change is in the way. One pasted back after a
1224−save wrote its embed out makes that save, the one that writes the embed back and those
1225−between one entry, so no undo stops where its file is embedded nowhere: one undo puts it
1226−back where it was. Where a file they touched changed from outside between two of them,
1227−they stay apart, as undoing them as one would drop that change.
1030+moment is under a second of typing in one block. Undo after an external change re-checks
1031+span hashes and refuses with a message if the target moved.
12281032
12291033---
12301034
@@ -1232,7 +1036,7 @@ they stay apart, as undoing them as one would drop that change.
12321036
12331037### 11.1 Writes
12341038
1235−- **Atomic**: write to `.<name>.fold-tmp` in the same directory, `fsync`, `rename`.
1039+- **Atomic**: write to `.<name>.notes-tmp` in the same directory, `fsync`, `rename`.
12361040 Never truncate in place.
12371041- **Span-preserving**: an edit replaces exactly the bytes of the affected node(s) — for a
12381042 property change, exactly the frontmatter lines that changed. Every other byte is copied
@@ -1244,32 +1048,14 @@ they stay apart, as undoing them as one would drop that change.
12441048### 11.2 Watcher and reload
12451049
12461050`notify`-based recursive watcher on the vault, 200 ms debounce, ignoring the patterns in
1247−§11.4. Only writes count — a file created, written, renamed or removed — never a file
1248−opened or read, as every reload reads them all. A reload runs only when the files differ
1249−from what the app last read or wrote (a file's text changed, a file came or went) or a new
1250−`*.sync-conflict-*.md` file appeared. The app's own writes, seen once they land, reload
1251−nothing.
1051+§11.4.
12521052
12531053- A changed file that the user is not editing is re-parsed; the cursor is re-attached by
1254− id, then key (§3.4), then the deepest step of the key that still exists.
1255−- The status line says what came in, by the top-level nodes it touched:
1256− `↻ changed outside fold: Inbox (+1 item)`.
1054+ id, then path, then nearest surviving ancestor.
12571055- A changed file with a built-in edit in progress: the editor saves its dirty blocks first;
12581056 a block whose span hash no longer matches is merged two-way (§5.2 step 5), then the file
1259− is re-parsed and, where the text the buffer shows changed, the buffer re-rendered around
1260− the cursor; a block cut there and not pasted back is deleted then (§5.2). A change that
1261− leaves that text as it was — to another node of the same file, as the phone adding to
1262− the Inbox in `root.md`, or to a file the buffer does not hold — leaves the editor as it
1263− is, a cut block still to paste.
1264−- A new `*.sync-conflict-*.md` file starts the merge flow (§12). What it merges without a
1265− pair is said as above, at startup too; a copy that brings nothing in, one it leaves alone
1266− or one the same as its file, says nothing, nor that the editor saved first for it. A copy
1267− it leaves alone or fails on is no change by itself, and is tried again with the next
1268− change. A copy that lands while a reload runs, as the next of a batch Syncthing delivers
1269− a moment apart, is new to the next reload. A block cut in the editor and not pasted back
1270− is moved on this device: a copy from before the cut, as the phone's `root.md` with an
1271− item added to the Inbox, still embeds it where it was, and is merged without that embed
1272− (§12.3): the block is not put back there, and the paste embeds it once.
1057+ is re-parsed and the buffer re-rendered around the cursor.
1058+- A new `*.sync-conflict-*.md` file starts the merge flow (§12).
12731059- A deleted file that was a block marks its embed broken; nothing is written.
12741060- Editing the same vault in Helix at the same time is a supported workflow.
12751061
@@ -1287,20 +1073,16 @@ during atomic writes.
12871073### 11.4 Ignore patterns
12881074
12891075Never parsed, never written, never deleted: dotfiles and dot-directories (`.git`, `.jj`,
1290−`.stfolder`, `.stversions`, `.stignore`, `.obsidian`), `.syncthing.*.tmp`, `*.fold-tmp`,
1076+`.stfolder`, `.stversions`, `.stignore`, `.obsidian`), `.syncthing.*.tmp`, `*.notes-tmp`,
12911077`*.tmp`, and any file not ending in `.md`. `*.sync-conflict-*.md` is parsed only by the merge
12921078engine.
12931079
12941080### 11.5 Trash
12951081
12961082Deleted subtrees and resolved `conflict:` blocks are written to
1297−`$XDG_STATE_HOME/fold/trash/<timestamp>-<id-or-name>.md` (device-local, never synced) before
1298−removal; editor text no save could take, to `<timestamp>-unsaved-<title>.md` (§10.6).
1299−`fold trash list|restore` manages it. `restore` moves an entry back into the vault, but an
1300−`unsaved-` entry has no id, so the vault would ignore it as a file (§6.4): it is printed
1301−instead, and stays in the trash. The content decides, not the name: a block file renamed
1302−`unsaved-….md` (§3.4) keeps its id and is moved back. The app never deletes user content
1303−without a trash copy.
1083+`$XDG_STATE_HOME/notes/trash/<timestamp>-<id-or-name>.md` (device-local, never synced) before
1084+removal. `notes trash list|restore` manages it. The app never deletes user content without
1085+a trash copy.
13041086
13051087---
13061088
@@ -1312,13 +1094,12 @@ without a trash copy.
13121094 winner plus a `name.sync-conflict-<date>-<time>-<device>.md` copy of the loser.
13131095- Therefore: keep concurrently-edited things in different files where possible (a
13141096 `Inbox` as a block; blocks), and make the app the thing that understands conflict files.
1315−- The vault's `.stignore` should contain at minimum: `.git`, `.jj`, `*.fold-tmp`, `*.tmp`.
1097+- The vault's `.stignore` should contain at minimum: `.git`, `.jj`, `*.notes-tmp`, `*.tmp`.
13161098
13171099### 12.2 Detection
13181100
1319−The watcher (or `fold merge`, or the startup scan) finds `X.sync-conflict-*.md` next to
1320−`X.md`, as the files are then: an `X.md` that came in with its copy is merged into. Several
1321−conflict files for the same `X.md` are merged oldest first.
1101+The watcher (or `notes merge`, or the startup scan) finds `X.sync-conflict-*.md` next to
1102+`X.md`. Several conflict files for the same `X.md` are merged oldest first.
13221103
13231104Before merging, the engine compares the two files' `id` keys. If they differ, this is not a
13241105conflict but a **prefix collision** — two blocks that happened to get the same filename
@@ -1329,11 +1110,10 @@ and nothing is lost.
13291110
13301111### 12.3 Merge inputs
13311112
1332−Ours `O` is `X.md`; theirs `T` is the conflict file, without its embeds of the blocks cut
1333−in the editor and not pasted back (§11.2) — or, for an in-session conflict (§5.2 step 5),
1334−the text the user just edited. There is no stored common ancestor (§11.3), so every merge
1335−is **two-way**: the engine can tell *that* two versions differ, not *which* side changed.
1336−One algorithm, one code path.
1113+Ours `O` is `X.md`; theirs `T` is the conflict file — or, for an in-session conflict
1114+(§5.2 step 5), the text the user just edited. There is no stored common ancestor (§11.3),
1115+so every merge is **two-way**: the engine can tell *that* two versions differ, not *which*
1116+side changed. One algorithm, one code path.
13371117
13381118### 12.4 Node-level merge
13391119
@@ -1347,23 +1127,21 @@ Both versions are parsed into trees (embeds unresolved; each file merges on its
13471127A retitled node therefore appears twice after a merge — once per title. Two-way merging
13481128never deletes, so this is the safe failure; delete the copy you don't want.
13491129
1350−**Per matched node**, for each of title, checkbox state, text (all its text children, with
1351−their positions among its child nodes, as one field), and — for the file's block — each
1352−frontmatter key independently: `O == T → take O`, otherwise **conflict**. A conflict pair is raised for every field that differs, even when
1130+**Per matched node**, for each of title, checkbox state, body (as text), and — for the
1131+file's block — each frontmatter key independently (`todo` included): `O == T → take O`,
1132+otherwise **conflict**. A conflict pair is raised for every field that differs, even when
13531133only one side touched it; the cost of having no per-device state is paid here, in
13541134resolution clicks, never in lost text.
13551135
13561136**Children** are merged as sequences of matched nodes: nodes present on one side only are
1357−insertions, placed relative to their matched neighbours and clamped by the ordering rule
1358−(§3.1). `O`'s text children are emitted where `O` has them. Nothing is ever treated as
1137+insertions, placed relative to their matched neighbours. Nothing is ever treated as
13591138deleted.
13601139
13611140**Conflict output**: the `O` version stays in place. The `T` version is written as a new
13621141block `<prefix>~<name>.md` (a fresh id; the name is `O`'s name) with frontmatter
13631142`conflict: "<device> <timestamp>"` (plus, for a block conflict, `T`'s own properties),
1364−and an embed to it is inserted as the next sibling of `O`, in the form matching `O`'s
1365−spelling (§4.7) — for a section, a heading embed after its whole subtree. The conflict block
1366−is therefore always the node immediately after the one it conflicts with; no other link
1143+and an embed to it is inserted as the next sibling of `O`. The conflict block is
1144+therefore always the node immediately after the one it conflicts with; no other link
13671145between them is stored. No text is ever lost: every node from `O` and `T` appears in the
13681146result exactly once.
13691147
@@ -1372,26 +1150,18 @@ algorithm is deterministic and idempotent.
13721150
13731151### 12.5 Resolution
13741152
1375−The conflict view (§10.7) lists every `conflict:` block, in outline order, each shown
1376−against the sibling before it, and the status line counts them. Resolving a pair:
1153+The conflict view (§10.8) lists every `conflict:` block, each shown against the sibling
1154+before it, and the status line counts them. Resolving a pair:
13771155
13781156- **keep ours** — delete the conflict block and its embed (to trash);
1379−- **keep theirs** — replace ours' title and children (text and nodes) with the conflict block's,
1157+- **keep theirs** — replace ours' title, body and children with the conflict block's,
13801158 and its frontmatter with the conflict block's minus `id` and `conflict`; then delete
13811159 the conflict block and its embed (to trash);
13821160- **keep both** — drop the `conflict` key; the block stays where it is as an ordinary
13831161 sibling.
13841162
13851163Unresolved pairs are ordinary blocks; they sync to every device and are visible in any
1386−editor, and can be lived with indefinitely. The app marks each copy `⚠` and folds it,
1387−leaves its tasks out of the counts, and offers it as no *Move to…* destination; `>` and a
1388−drop put nothing under it (§10.1, §10.3). No verb puts a node between a copy and the node it
1389−follows, which the copy would then pair with, nor takes one away from the other: `J`/`K`,
1390−*Move to…*, `za`, `>`, `<` and a drop move the two as one, from either side, and into
1391−neither; `d` on the node deletes its copies with it, and on a copy that copy alone, as
1392−keep ours does; *clear done* keeps a done node whose copy it keeps; `~` is refused on
1393−either side until the pair is resolved. A node added, pasted, dropped or outdented between
1394−them goes after the copy.
1164+editor, and can be lived with indefinitely.
13951165
13961166### 12.6 History (outside the app)
13971167
@@ -1404,16 +1174,16 @@ notices it.
14041174
14051175## 13. CLI
14061176
1407−All commands take `--vault PATH` (default: `$FOLD_VAULT`, else the nearest ancestor of
1408−`$PWD` containing `root.md`, else `~/fold`).
1177+All commands take `--vault PATH` (default: `$NOTES_VAULT`, else the nearest ancestor of
1178+`$PWD` containing `root.md`, else `~/notes`).
14091179
14101180| Command | Purpose |
14111181|---|---|
1412−| `fold [--keys normal\|vim\|helix]` | open the TUI; creates `root.md` if the directory is empty (§4.1.1); `--keys` picks the editor's keymap (§10.6) |
1413−| `fold capture [TEXT] [--to TARGET] [--task]` | append to the inbox (stdin if no TEXT) |
1414−| `fold check [--fix]` | diagnostics (§15.7); `--fix` rewrites the vault in canonical form (§4.2) and repairs filenames (§6.4) |
1415−| `fold merge [--dry-run]` | process sync-conflict files non-interactively; list leftovers |
1416−| `fold trash list \| restore ID` | trash management |
1182+| `notes` | open the TUI; creates `root.md` if the directory is empty (§4.1.1) |
1183+| `notes capture [TEXT] [--to TARGET] [--task]` | append to the inbox (stdin if no TEXT) |
1184+| `notes check [--fix]` | diagnostics with source spans (§15.7); `--fix` rewrites the vault in canonical form (§4.2) and repairs filenames (§6.4) |
1185+| `notes merge [--dry-run]` | process sync-conflict files non-interactively; list leftovers |
1186+| `notes trash list \| restore ID` | trash management |
14171187
14181188That is the whole CLI: the entry points a shell, a hotkey, a cron job on the homelab, or a
14191189recovery session needs. Everything else is a TUI verb.
@@ -1422,27 +1192,23 @@ recovery session needs. Everything else is a TUI verb.
14221192
14231193## 14. Settings
14241194
1425−There are no vault settings. Everything that could be one is either fixed behaviour or
1426−content: the inbox is the section titled `Inbox` (§7);
1195+There are no vault settings. Everything that would have been one is either fixed behaviour
1196+or content: the inbox is the section titled `Inbox` (§7);
14271197filenames are an id prefix plus the title; names keep Unicode; ids are four words in
14281198frontmatter (§6.4); making blocks
14291199are property-triggered or manual only; `done:` records a date; conflict copies are
14301200blocks marked `conflict:`. Each was a knob nobody needs to agree on with a file. Two vaults that
14311201contain the same Markdown behave identically.
14321202
1433−There are no device settings either. The app reads no file outside the vault but the view it
1434−remembers (§10.1). What other tools keep in configuration is:
1203+There are no device settings either. The app reads no file outside the vault. What used to
1204+be configuration is now:
14351205
1436−- the vault: `--vault PATH`, else `$FOLD_VAULT`, else the nearest ancestor of `$PWD`
1437− containing `root.md`, else `~/fold`;
1438−- the trash location: `$XDG_STATE_HOME/fold/trash/` (§11.5);
1439−- the editor's keymap: `--keys`, else `$FOLD_KEYS` (`normal`, `vim`, `helix`), else
1440− `normal` (§10.6);
1441−- everything visual: fixed, or a toggle the view remembers per vault (`zp`, `zw`, `zd`,
1442− §10.1), or one for the session (`zr`).
1206+- the vault: `--vault PATH`, else `$NOTES_VAULT`, else the nearest ancestor of `$PWD`
1207+ containing `root.md`, else `~/notes`;
1208+- the trash location: `$XDG_STATE_HOME/notes/trash/` (§11.5);
1209+- everything visual: fixed (§10.1) or a session toggle (`zd`, `zr`).
14431210
1444−The only things the app keeps outside the vault are the trash and the remembered view,
1445−both on the device and never synced.
1211+The only thing the app keeps outside the vault is the trash.
14461212
14471213---
14481214
@@ -1452,15 +1218,12 @@ both on the device and never synced.
14521218
14531219```
14541220crates/
1455− fold-core/ parsing, tree, render/splice, index, store, merge, op log — no TUI deps
1456− fold-tui/ ratatui application
1457− fold-cli/ clap binary; depends on both
1458− fold-ffi/ a session over fold-core for other languages, through UniFFI
1459−android/ the Android app, over fold-ffi
1221+ notes-core/ parsing, tree, render/splice, index, store, merge — no TUI deps
1222+ notes-tui/ ratatui application
1223+ notes-cli/ clap binary; depends on both
14601224```
14611225
1462−`fold-core` is the API every front end uses; `fold-ffi` wraps it in a session that holds
1463−what the TUI holds between keys, for the Android app or a Helix extension.
1226+`notes-core` is the stable API a future Android client or Helix extension would use.
14641227
14651228### 15.2 Core types (sketch)
14661229
@@ -1472,8 +1235,9 @@ pub enum TaskState { Open, Done }
14721235pub struct Node {
14731236 kind: Kind,
14741237 title: String,
1475− task: Option<TaskState>, // any node, block or not: the checkbox on its title line
1476− content: Vec<Content>, // ordered children: text runs and nodes (§3.1)
1238+ task: Option<TaskState>, // any node; from the checkbox, or `todo` for a block
1239+ body: Vec<String>, // raw lines, opaque
1240+ children: Vec<NodeId>,
14771241 parent: Option<NodeId>,
14781242 file: FileId,
14791243 span: Span, // bytes in `file`: title line through subtree
@@ -1482,24 +1246,17 @@ pub struct Node {
14821246
14831247pub struct Block {
14841248 id: Option<Id>, // None only for root.md
1485− name: String, // slug of the title when made (or at the last `check --fix`)
1249+ name: String, // slug of the title at last rename
14861250 path: RelPath, // e.g. "racfer~order-new-switch.md"
14871251 props: IndexMap<String, String>, // top-level `key: value` lines, order-preserving; includes `id`
14881252 frontmatter_raw: String, // verbatim, for lossless rewrite
14891253 edge_span: Span, // the embed line in the parent file
14901254}
14911255
1492−pub enum Content { Text(Span), Node(NodeId) } // `(Text | Item)* Section*` (§3.1)
1493−
14941256pub struct Span { start: usize, end: usize }
14951257
14961258pub struct Tree { nodes: Vec<Node>, root: NodeId, files: Vec<FileState> }
14971259pub struct Cursor { path: Vec<NodeId> } // zipper-style focus; all outline verbs are cursor ops
1498−pub enum NodeKey { // survives re-parses (§3.4)
1499− Root,
1500− Id(Id),
1501− Path { block: Option<Id>, steps: Vec<(String, usize)> }, // title + ordinal among same-titled siblings
1502−}
15031260```
15041261
15051262Levels and indents are computed from ancestry, never stored.
@@ -1515,17 +1272,16 @@ exact spans, lossless round-tripping, and a small canonical subset — not a ful
15151272`pulldown-cmark` may be used later for inline styling in the reading pane only.
15161273
15171274Reading tolerance (setext headings, `*` bullets, tabs, `[X]`, `[-]`) is handled in the lexer and
1518−recorded per node as "non-canonical", which `fold check` reports and touching rewrites.
1275+recorded per node as "non-canonical", which `notes check` reports and touching rewrites.
15191276
15201277### 15.4 Dependencies
15211278
1522−`ratatui`, `crossterm`, `notify` (watcher), `signal-hook` (§10.6),
1523−`tree-sitter` + `tree-sitter-highlight` and the grammar crates of §10.9 (code highlighting), `jiff`
1524−(today's date), `clap`, `indexmap`, `similar` (line diff, to find a block's span after an
1525−outside change, §5.2), `blake3`, `unicode-normalization`, `unicode-width`, `directories` (XDG),
1526−`getrandom` (64-bit ids; the `@p` syllable tables are a 512-entry constant in `fold-core`),
1527−`uniffi` (`fold-ffi`), `tempfile`, `assert_cmd` + `predicates` (tests). The built-in editor
1528−(§10.6) and the filter's fuzzy match (§10.5) are fold's own.
1279+`ratatui`, `crossterm`, `tui-textarea` (built-in editor), `notify` (watcher), `jiff`
1280+(today's date), `clap`, `indexmap`, `nucleo` (fuzzy
1281+filter), `similar` (sequence alignment and diff3 for merge), `blake3`, `tempfile`,
1282+`unicode-normalization`, `unicode-width`, `directories` (XDG),
1283+`getrandom` (64-bit ids; the `@p` syllable tables are a 512-entry constant in `notes-core`),
1284+`ariadne` (diagnostics), `proptest` + `insta` (tests).
15291285
15301286### 15.5 Performance targets
15311287
@@ -1541,15 +1297,11 @@ outside change, §5.2), `blake3`, `unicode-normalization`, `unicode-width`, `dir
15411297
15421298### 15.6 Testing
15431299
1544−The whole design rests on a few laws, and the tests check them on examples; property-based
1545−tests over generated trees are the natural next step:
1300+Property-based, since the whole design rests on a few laws:
15461301
15471302- `parse(render(t, 1, false))` equals `t` re-levelled, for all generated trees `t`.
15481303- `render(parse(s), 1, false) == s` for all canonical blocks `s`, frontmatter included.
15491304- `canonicalize` is idempotent.
1550−- Every verb (`J`/`K`, `>`/`<`, `~`, `p`/`P`, refile, capture, `n`/`N`, make block, merge)
1551− leaves every node's children satisfying `(text | item)* section*`, keeps every other node's
1552− parent, and moves no text child it was not asked to move.
15531305- `splice(node, render(node, 1, true))` is a no-op on disk, for any node.
15541306- Every splice writes exactly one file; a save writes one splice per dirty block.
15551307- `render(make_block(n), 1, false)` equals `render(n, 1, false)` plus the generated frontmatter,
@@ -1562,11 +1314,9 @@ of real-world Markdown (Obsidian, Logseq, FSNotes exports) that must parse witho
15621314
15631315### 15.7 Diagnostics
15641316
1565−`fold check` reports, file by file: non-canonical syntax, empty
1317+`notes check` reports, with `ariadne`-rendered source spans: non-canonical syntax, empty
15661318titles, titles containing `/`, broken, duplicate or cyclic embeds, embeds with children in the
1567−parent file, embeds whose form (bare or heading) does not match their block's spelling,
1568−heading embeds whose level is not the level of their position, text directly after a child
1569−node with no blank line, column-0 lines adopted by a block's root, malformed block files, invalid or duplicate `id` keys, ignored `.md` files,
1319+parent file, malformed block files, invalid or duplicate `id` keys, ignored `.md` files,
15701320filenames whose prefix or name no longer match their id or title, frontmatter outside
15711321byte 0, `due` or `done` values that are not ISO dates, and unresolved conflict blocks.
15721322The TUI
@@ -1581,18 +1331,20 @@ gets wrong. What other tools see:
15811331
15821332| Tool | What it sees | Notes |
15831333|---|---|---|
1584−| `rg`, `fd`, Helix | plain text | `rg '\[ \]'` is the open task list; `rg '^done: 2026-09'` the blocks finished this month; `rg '^due:'` the dated ones |
1585−| Any phone editor | plain text | capture by appending a `- ` line to the inbox block's file, at any indent: a column-0 line is adopted by the block's root (§4.9); the app adds nothing to it |
1586−| Obsidian, Logseq, FSNotes | mostly readable, structurally wrong | frontmatter as properties and `- [ ]` bullets render; `![[id]]` shows as a broken embed (a heading embed as a heading holding one), `## [ ]` as a literal heading, `#######` as a paragraph, indented headings as code. Fine for reading a file; do not edit structure there |
1334+| `rg`, `fd`, Helix | plain text | `rg '\[ \]'` and `rg '^todo: open'` together are the open task list; `rg '^done: 2026-09'` the blocks finished this month; `rg '^due:'` the dated ones |
1335+| Any phone editor | plain text | capture by appending a `- ` line to the inbox block's file; the app adds nothing to it |
1336+| Obsidian, Logseq, FSNotes | mostly readable, structurally wrong | frontmatter as properties and `- [ ]` bullets render; `![[id]]` shows as a broken embed, `## [ ]` as a literal heading, `#######` as a paragraph, indented headings as code. Fine for reading a file; do not edit structure there |
15871337
15881338---
15891339
15901340## 17. Non-goals for 1.0
15911341
1592−Encryption; CRDT / operation-log sync;
1342+Encryption; CRDT / operation-log sync; a mobile client (the file format is its contract);
15931343a query language, saved views and `--json` output (the ```` ```query ```` fence is reserved); attachment management beyond ignoring `assets/`; full Markdown
15941344rendering or WYSIWYG; plugins; task recurrence; spaced repetition; whiteboards; multiple
1595−vaults open at once; general transclusion; an external `$EDITOR` handoff; properties on non-blocks; export or lowering
1345+vaults open at once; general transclusion; an external `$EDITOR` handoff (designed:
1346+resolved subtree with frontmatter inline under each block's title, ids included, replaced
1347+wholesale on exit); properties on non-blocks; export or lowering
15961348to standard Markdown (the four non-standard forms of §4.2 are the whole gap); a cancelled
15971349task state; wiki-links between nodes and backlinks
15981350(`[[…]]` is reserved, §4.6); rendering the format correctly in third-party editors.
@@ -1603,81 +1355,105 @@ task state; wiki-links between nodes and backlinks
16031355
16041356| # | Deliverable | Exit criterion |
16051357|---|---|---|
1606−| M0 | `fold-core`: parser incl. frontmatter, tree, render/splice, canonicalize, ids and names | round-trip laws pass under proptest; `fold check` works |
1358+| M0 | `notes-core`: parser incl. frontmatter, tree, render/splice, canonicalize, ids and names | round-trip laws pass under proptest; `notes check` works |
16071359| M1 | TUI: outline + reading panes, zoom, fold, move, promote/demote, refile, filter box, subtree editing (`e`), undo | daily-drivable on a single `root.md` |
16081360| M2 | Blocks and properties: `s`, property-triggered blocks, task blocks | a task can be given a due date in the editor and its file round-trips |
1609−| M3 | Tasks: toggling on any node, `done:` stamping, dates, clear-done, filter box | replaces a TaskPaper file |
1361+| M3 | Tasks: toggling on any node, `todo:` and `done:` stamping, dates, clear-done, filter box | replaces a TaskPaper file |
16101362| M4 | Inbox and capture; watcher and reload | safe to edit in Helix and the TUI at once |
1611−| M5 | Sync: conflict detection, two-way node-level merge, `conflict:` blocks, conflict view, `fold merge` | phone + laptop + homelab over Syncthing for two weeks without data loss |
1363+| M5 | Sync: conflict detection, two-way node-level merge, `conflict:` blocks, conflict view, `notes merge` | phone + laptop + homelab over Syncthing for two weeks without data loss |
16121364| M6 | CLI polish, diagnostics, docs | 1.0 |
16131365
16141366---
16151367
1616−## 19. Decisions
1368+## 19. Open decisions
16171369
1618−1. **Name.** `fold`: the binary, the crate prefix and the state directory.
1370+1. **Name.** Binary `notes` is a placeholder.
161913712. **Dated inbox, ISO titles, fixed.** Capture creates `## YYYY-MM-DD` under `Inbox`. There
16201372 is no format choice and no way to turn it off except `--to`. Days append oldest-first; a
16211373 long-lived inbox is a long section, which is what `s` (make block) and *clear done* are for.
1622−3. **Filenames are named once.** A title change does not rename the file: renames sync as
1623− delete-plus-create and race with edits on other devices. The id prefix identifies the
1624− file; the name is a hint that `fold check --fix` refreshes on request.
1625−4. **Id format.** Four-word `@p` names (64 bits): collision-free in practice, with three words
1626− of slack for the filename prefix to grow into. Pronounceable, typeable from memory, and
1627− self-validating against the syllable tables; nobody is expected to type all four.
1628−5. **Task sections.** Any node can be a task, so a project heading can be checked off. The
1374+3. **Filenames follow titles, always.** A title change is a file rename (Syncthing handles
1375+ renames by block reuse, so cheap but visible) and nothing else, since the id lives in the
1376+ file. The id prefix stays in front through renames, so it is the one part of a filename
1377+ that never moves.
1378+3b. **Id format.** Four-word `@p` names (64 bits). Two words (32 bits) were enough for
1379+ identity but left the one-word filename prefix with only 16 bits of room to grow; four
1380+ words make the id collision-free in practice and give the prefix three words of slack.
1381+ Chosen for being pronounceable, typeable from memory, and self-validating against the
1382+ syllable tables; nobody is expected to type all four.
1383+4. **Task sections.** Any node can be a task, so a project heading can be checked off. The
16291384 derived count is still shown beside it; there is no roll-up in either direction (§8.3).
1630−6. **One spelling of task state.** The checkbox, for every node, so `rg '\[ \]'` finds every
1631− open task and making a block never converts anything. Frontmatter holds only what a
1632− checkbox cannot: `done:`.
1633−7. **Two task states.** Open and done. "Won't do" is a deleted line or a done line; a third
1634− state would cost a value in every table, a second date key and a key binding. `[-]` is
1635− read as done.
1636−8. **No un-block.** There is no inverse of making a block: it would have to discard the
1637− frontmatter, and a verb whose defining feature is discarding data is a footgun. The
1638− manual route (yank, paste, delete) makes the loss explicit.
1639−9. **No export.** The resolved render is what the reading pane shows; a standalone-Markdown
1640− exporter is future work with a known scope (§4.2's four forms).
1641−10. **No fuzzy matching in merge.** A node retitled on one device and edited on another
1642− comes out of a merge as two nodes. A fuzzy title match would need a tunable threshold and
1643− could pair the wrong nodes; a duplicate is cheaper to fix than a wrong pairing.
1644−11. **Two-way merge, no cache.** A per-device base copy would make sync merges three-way,
1645− but it would be the only per-device state (§11.3). Sync conflicts therefore produce a
1646− conflict pair for every differing field; the mitigation is structural — keep
1647− concurrently-edited things in separate files (§12.1), so conflicts are small and rare.
1648−12. **Text is edited as text, properties as a form.** One subtree edit in a text box covers
1649− titles, bodies and structure. Frontmatter is a storage format, and showing it would
1650− surface files, so the property editor is the one non-text edit. The built-in editor is a
1651− text box; if it wants to become a Markdown editor, stop.
1652−13. **Inbox by title.** Capture finds the inbox by the title `Inbox`. Renaming it means the
1653− next capture creates a new one. A hidden marker property would be the only setting.
1654−14. **No config file.** Presentation choices are fixed or session toggles; a config format, a
1655− lookup order and a `fold config` command are not worth it. A real per-device need is
1656− one environment variable: the editor's keymap is `$FOLD_KEYS` (§10.6), and a
1657− colour-blind palette, say, would be another.
1658−15. **Windows.** Slugs and atomic writes are Windows-safe by design; nothing else is tested.
1659−16. **No links or query language in 1.0.** Embeds prove the id machinery, and links can be
1660− built on it without changing the file format, which is why `[[…]]` is reserved rather
1661− than free. The likely query shape is "everything is a key" (`due:..today`, text is
1662− literal, `-` negates, `..` ranges); until then the filter box is the only search and the
1663− `query` fence is reserved.
1664−17. **No per-node timestamps.** One metadata mechanism is worth more than inline annotations
1665− such as `@done(…)`. An inline task that needs a date becomes a block; the day section is
1666− the only creation stamp anything else gets.
1667−18. **Flat vault.** The app never creates a directory and ignores any it finds. Hierarchy is
1668− embeds, and one place for files keeps prefixes unique vault-wide with no per-directory
1669− rule.
1670−19. **Prefix collisions.** Two blocks whose ids share a first word are `racfer~notes.md`
1385+4b. **Two spellings of task state.** A checkbox for nodes that have no file, `todo:` for
1386+ nodes that do. One mechanism was considered twice: all-frontmatter would make every task
1387+ a file, all-checkbox puts structure on a title line the frontmatter already describes.
1388+ The split costs one conversion when a block is made and nothing else.
1389+5. **Two task states.** Open and done. Cancelled was removed: it was a third value in every
1390+ table, a second date key, and a key binding, for a distinction between "done" and
1391+ "won't do" that a deleted line or a done line expresses well enough. `[-]` is read as
1392+ done for old vaults.
1393+6. **No un-block.** Earlier drafts had an inverse of making a block that dropped the frontmatter. Removed:
1394+ a verb whose defining feature is discarding data is a footgun, and the manual route
1395+ (yank, paste, delete) makes the loss explicit. Revisit only if vaults fill with
1396+ blocks nobody wanted.
1397+7. **No export.** `notes render` and the `--plain` lowering were removed for 1.0. The
1398+ resolved render is now only what the reading pane reads; a standalone-Markdown exporter
1399+ is a later feature with a known scope (§4.2's four forms).
1400+8. **No fuzzy matching in merge.** A node retitled on one device and edited on another
1401+ comes out of a merge as two nodes. The fuzzy title match that would have paired them
1402+ needed a tunable threshold and could pair the wrong nodes; a duplicate is cheaper to
1403+ fix than a wrong pairing, so the step was removed.
1404+8b. **Two-way merge, no cache.** Shadow copies would have made sync merges three-way and
1405+ resolved most differences automatically; they were the only per-device state and were
1406+ removed with it (§11.3). Sync conflicts now produce a conflict pair for every differing
1407+ field. The mitigation is structural: keep concurrently-edited things in separate files
1408+ (§12.1), so conflicts are small and rare. If they turn out to be neither, a per-device
1409+ base is the thing to bring back.
1410+9. **Text is edited as text, properties as a form.** Earlier drafts had a title editor and
1411+ a block editor beside an external-editor handoff; those collapsed into one subtree edit,
1412+ and the external handoff was then dropped from 1.0 (§17). Properties briefly appeared in
1413+ the text as `---` blocks and were taken out again: frontmatter is a storage format, and
1414+ showing it would surface files. The property editor is the one non-text edit. The
1415+ built-in editor is a text box; if it wants to become a Markdown editor, stop.
1416+10. **Inbox by title.** Capture finds the inbox by the title `Inbox`. Renaming it means the
1417+ next capture creates a new one. A hidden marker property was the alternative and would
1418+ have been the last setting.
1419+10b. **No config file.** Seven presentation keys, each already available from the environment
1420+ or replaceable by a key binding, were not worth a file format, a lookup order, and a
1421+ `notes config` command. If a real per-device need appears (a colour-blind palette, say)
1422+ it can come back as one environment variable.
1423+11. **Windows.** Slugs and atomic writes are Windows-safe by design; nothing else is tested.
1424+12. **No links in 1.0.** Storing links by id, displaying the live title, and rewriting labels
1425+ on rename were all designed and then cut for the first implementation. Embeds already
1426+ prove the id machinery; links can return on top of it without changing the file format,
1427+ which is why `[[…]]` is reserved rather than free.
1428+13. **No per-node timestamps.** Title-line annotations (`@done(…)`, `@created(…)`) were
1429+ tried in 0.15 and removed: one metadata mechanism is worth more than inline completion
1430+ dates. An inline task that needs a date becomes a block; the day section is the
1431+ only creation stamp anything else gets.
1432+13b. **Flat vault.** `--as dir/` was removed; the app never creates a directory and ignores
1433+ any it finds. Hierarchy is embeds, and one place for files keeps prefixes unique
1434+ vault-wide with no per-directory rule.
1435+14. **Prefix collisions.** Two blocks whose ids share a first word are `racfer~notes.md`
16711436 and `racfer-wolsun~order-new-switch.md`, regardless of their titles; the prefix grows only
1672− as far as needed and never shrinks (§6.4). Uniqueness is judged on the prefix alone, so
1673− the prefix is a complete address and titles can change freely. Roughly one file in 256
1674− needs a second word from day one, which is cheaper than any rule that depends on titles;
1675− the full id is too long for a name, the bare slug too collision-prone across devices, and
1676− a numeric `-2` meaningless.
1677−20. **Ownership travels with lines.** Each editor line is tagged with its block, and blocks
1678− are saved as the cursor leaves them. That needs no re-matching of nodes after an edit,
1679− shows no seam between files, and still writes each file from its own text. An external
1680− editor cannot carry the tags, so there is none in 1.0 (§17).
1681−21. **Third-party editors are readers, not editors.** Once the format owns syntax, Obsidian
1682− and friends are not a safe place to restructure a vault. Editing a paragraph there is
1683− fine; moving headings is not. This is the price of §1 principle 2 and is accepted.
1437+ as far as needed and never shrinks (§6.4). Uniqueness is judged on the prefix alone, not
1438+ the whole filename, so the prefix is a complete address and titles can change freely.
1439+ The cost is that roughly one file in 256 needs a second word from day one; that was
1440+ judged cheaper than a rule that depends on titles. The full id was rejected as too long
1441+ for a name, the bare slug as too collision-prone across devices, and a numeric `-2` as
1442+ meaningless.
1443+17. **Ownership travels with lines.** Three other models were tried: distributing a
1444+ multi-file edit back by re-matching nodes (needs heuristics and a prompt when they
1445+ fail), showing nested blocks as placeholders (leaks files into the text), and locking
1446+ every block but one (a seam the user has to work around). Tagging each buffer line with
1447+ its block and saving blocks as the cursor leaves them needs no matching, shows no seam,
1448+ and still writes each file from its own text. An external editor cannot carry tags;
1449+ the design for one (frontmatter inline, ids included, subtree replaced on exit) is
1450+ parked in §17.
1451+16. **No query language in 1.0.** Several syntaxes were sketched — boolean with `@key`, a
1452+ search box with `is:` and `key:value`, and "everything is a key" (`todo:open
1453+ due:..today`, text is literal, `-` negates, `..` ranges). The last is the likely
1454+ shape when it returns. Until then the filter box is the only search and the `query`
1455+ fence is reserved.
1456+15. **Third-party editors are readers, not editors.** Once the format owns syntax, Obsidian
1457+ and friends stop being a safe place to restructure a vault. Editing a body paragraph
1458+ there is still fine; moving headings is not. This is the price of §1 principle 2 and is
1459+ accepted.