Where a live document goes: through Notes, onto disk, and into the index.
Leaf.Collab.Store.File ships with Leaf and describes exactly this
arrangement — the file is the document, its hash is checked before writing,
and a note edited from outside is answered with a conflict rather than
overwritten. It is not used here for one reason: it writes the file and stops
there.
A note is not only its bytes. Its title, front matter, tags, outgoing links
and search vector are all derived from those bytes, and writing the file
without re-deriving them leaves Postgres describing a note that no longer
exists — invariant 1 says the index is disposable, not that it may be wrong.
Notes.update_content/4 already writes to disk and indexes what landed, and
already carries the stale guard, so this store is a thin adapter onto it.
The two callbacks
save/2 is a no-op. It is called on every change and is meant for a store
cheap enough to write to constantly; a file plus a reindex is the expensive
copy, which is what flush/2 is for. If losing seconds of a session ever
matters more than it does now, save/2 is where a row in a table would go.
flush/2 runs when the writing pauses, at a bounded interval while it
continues, and once when the room stops.
Conflict
{:error, :conflict} means the file moved underneath the session — someone
edited the note outside the editor, or a rescan re-stamped it. The room stops
flushing and keeps the document, which is the right answer: the work in the
file is somebody's, and this session's copy is not more entitled to exist.
Resolving it needs a person.
The note row is re-read on every flush rather than held, because the previous
flush changed it: Notes.update_content/4 compares the file's hash against
the row's, so a stale row would report a conflict against this session's own
last write.