aboutsummaryrefslogtreecommitdiffstats
path: root/docs
diff options
context:
space:
mode:
Diffstat (limited to 'docs')
-rw-r--r--docs/superpowers/specs/2026-10-16-notes-design.md29
1 files changed, 19 insertions, 10 deletions
diff --git a/docs/superpowers/specs/2026-10-16-notes-design.md b/docs/superpowers/specs/2026-10-16-notes-design.md
index 459b526..b8524a2 100644
--- a/docs/superpowers/specs/2026-10-16-notes-design.md
+++ b/docs/superpowers/specs/2026-10-16-notes-design.md
@@ -91,10 +91,12 @@ Opened by a row (existing note) or **New note**.
renders the body as Markdown; Edit shows the raw Markdown in a `TextEdit`.
An existing note opens in Preview, a new one in Edit.
- **Save** is two clicks, so the note is on disk before git is involved: the
- first writes it and the button becomes **Commit & push?**; the second runs
- the git half (see Git) and returns to the list. Leaving with the second click
- unmade keeps the saved note and does not push it. With git off there is no
- git half, so Save writes and returns to the list in one click.
+ first writes it and the button becomes **Commit & push?**; the second writes
+ whatever the editor holds then, commits and pushes (see Git), and returns to
+ the list. The editor stays editable between the clicks, so the second has to
+ write as well: pushing alone would drop an edit made after the first. Leaving
+ with the second click unmade keeps the saved note and does not push it. With
+ git off there is no second half, so Save writes and returns in one click.
- **Cancel** returns to the list without saving. **Delete**, existing notes
only, asks first: the first click arms the button, the second removes the
note and pushes the removal. Nothing is destroyed until the second click, a
@@ -109,9 +111,15 @@ Opened by a row (existing note) or **New note**.
## Writes
All index writes go through the module's `db.json` `FileView` with
-`atomicWrites: true`, so a write that dies mid-way cannot leave a half
-`db.json` for `notes.sh` to trip on. A body is written with a `FileView` on
-its own path, which creates the file.
+`atomicWrites: true` and `blockWrites`, so a write that dies mid-way cannot
+leave a half `db.json` for `notes.sh` to trip on, and its outcome is known when
+the call returns.
+
+A body is written by a `Process` running `cat > <file>` with the text on stdin,
+not by a `FileView`. `setText` with an **empty** string, on a path that view has
+not loaded, creates no file at all and reports no error, which put a note with
+an empty body into `db.json` with no file beside it. `cat` takes any content at
+any length, including none, and its exit code is the outcome.
**A write is refused when `db.json` cannot be read as the index.** That is not
hypothetical: `git pull --rebase --autostash` resolves a remote `db.json`
@@ -122,9 +130,10 @@ and writing it back would delete every title, so the write instead reports
git to resolve. `Notes.valid` is the check; `Notes.parse` stays tolerant for
display, returning an empty store for the same input.
-A write that fails is reported, not assumed to have landed: both writes block
-(`blockWrites`), because `setText` is asynchronous otherwise, and a failure
-that nobody observes reports a save that never happened.
+A write that fails is reported, not assumed to have landed: the index write
+blocks (`blockWrites`) and the body write's `cat` exit code is checked, because
+`setText` is asynchronous otherwise and a failure nobody observes reports a
+save that never happened.
- **Create**: the first click pulls first (see Git), then re-reads `db.json`
so the append is based on what the pull brought in, not on what was read