aboutsummaryrefslogtreecommitdiffstats
path: root/desktop
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-16 21:08:04 +0200
committerDanilo M. <danix@danix.xyz>2026-10-16 21:08:04 +0200
commitcf99dde4805f1792d6a56156b6a0338aa684005c (patch)
treec3c04b8e0457b855d50d2e0ca41ffe417630ed3a /desktop
parentd423ec2646d444523fd44c92864988445cf072d2 (diff)
downloadquickshell-cf99dde4805f1792d6a56156b6a0338aa684005c.tar.gz
quickshell-cf99dde4805f1792d6a56156b6a0338aa684005c.zip
notes: an empty note body gets a file, and the second click writes
Reported from use: a note created with only a title then saved left a db.json entry with no file beside it, and reopening it said the file was missing. Cause: FileView.setText("") on a path that view has not loaded creates no file at all and reports no error. The index write (always non-empty JSON) was fine, so only the body was lost, which is why it looked like a missing file rather than a failed save. Bodies are now written by a Process running cat > <file> with the text on stdin: verified against empty and non-empty payloads, consecutive writes on one Process, and a store with no db.json at all. The exit code is the real outcome, so a refused write reports it. Second report in the same flow: the editor stays editable after the first click, so typing more and then pressing Commit & push dropped the edit, the push carrying only what the first click wrote. The second click now writes the current title and body before it commits and pushes.
Diffstat (limited to 'desktop')
-rw-r--r--desktop/modules/notes/NotesModule.qml45
-rw-r--r--desktop/modules/notes/NotesPage.qml25
-rw-r--r--desktop/modules/notes/README.md15
3 files changed, 54 insertions, 31 deletions
diff --git a/desktop/modules/notes/NotesModule.qml b/desktop/modules/notes/NotesModule.qml
index 082f02c..992cbfa 100644
--- a/desktop/modules/notes/NotesModule.qml
+++ b/desktop/modules/notes/NotesModule.qml
@@ -150,27 +150,38 @@ Module {
if (done) done(ok, ok ? "" : mod.dbView.saveError);
}
- // A note body. notes.sh keeps the title out of the file, so this is the
- // body and nothing else. setText writes the path directly and needs no
- // prior load; reading the file back is what would need one, and nothing
- // here reads it back.
- property FileView bodyView: FileView {
- blockWrites: true
- printErrors: false
- property string saveError: ""
- onSaved: saveError = ""
- onSaveFailed: error => {
- console.warn("notes: note body write failed:", error);
- saveError = "the note's file could not be written";
+ // A note body, written through a Process rather than a FileView.
+ // FileView.setText() with EMPTY text on a path that view has not loaded
+ // writes no file at all and reports no error, which is how a note created
+ // with an empty body ended up in db.json with no file beside it. cat reads
+ // stdin, so any content and any length works, including none, and the exit
+ // code is the real outcome. Clearing stdinEnabled after the write is what
+ // gives cat its EOF.
+ property Process bodyWriter: Process {
+ stdinEnabled: true
+ property string data: ""
+ property var done: null
+ property string err: ""
+ onStarted: {
+ mod.bodyWriter.write(mod.bodyWriter.data);
+ mod.bodyWriter.stdinEnabled = false;
+ }
+ onExited: code => {
+ const d = mod.bodyWriter.done;
+ mod.bodyWriter.done = null;
+ if (d) d(code === 0, code === 0 ? "" : (mod.bodyWriter.err || "the note's file could not be written"));
}
+ stderr: StdioCollector { onStreamFinished: mod.bodyWriter.err = this.text }
}
function writeBody(path, text, done) {
- mod.bodyView.path = path;
- mod.bodyView.saveError = "";
- mod.bodyView.setText(text);
- const ok = mod.bodyView.saveError === "";
- if (done) done(ok, ok ? "" : mod.bodyView.saveError);
+ mod.bodyWriter.done = done;
+ mod.bodyWriter.data = text;
+ mod.bodyWriter.err = "";
+ mod.bodyWriter.command = ["sh", "-c", 'cat > "$1"', "sh", path];
+ mod.bodyWriter.stdinEnabled = true;
+ mod.bodyWriter.running = false;
+ mod.bodyWriter.running = true;
}
// The path of a note's body, "" when the entry's file is not a plain
diff --git a/desktop/modules/notes/NotesPage.qml b/desktop/modules/notes/NotesPage.qml
index 585fcfb..1a82254 100644
--- a/desktop/modules/notes/NotesPage.qml
+++ b/desktop/modules/notes/NotesPage.qml
@@ -19,9 +19,11 @@ import "Notes.js" as Notes
//
// Save is two clicks and each click does one thing. The first writes the note
// and, when that has finished, arms the button as "Commit & push?"; the second
-// commits and pushes and returns to the list. Arming the second click only
-// after the write completes is what stops a quick second click from committing
-// a note the first click has not written yet.
+// writes whatever the editor holds now, then commits and pushes, then returns
+// to the list. The second click writes as well because the editor stays
+// editable in between: pushing without writing would throw away an edit made
+// after the first click. Arming only after the first write completes is what
+// stops a quick second click from committing a note that is not written yet.
//
// Nothing is remembered between visits: leaving the editor with text that was
// not saved drops it, which is the clipboard page's own rule.
@@ -78,12 +80,19 @@ Item {
// The note is already on disk and only the push is owed, so this click
// pushes rather than writing a second note.
if (page.awaiting !== "") {
- const id = page.awaiting === "create" ? page.owner.lastCreatedId : page.editing.id;
+ // The editor stays editable after the first click, so whatever was
+ // typed since is written before it is pushed: the push alone would
+ // leave that edit behind.
+ const kind = page.awaiting;
+ const id = kind === "create" ? page.owner.lastCreatedId : page.editing.id;
page.busy = true;
- page.owner.commitPush(page.awaiting, id, (ok, msg) => {
- page.busy = false;
- if (!ok) { page.owner.lastError = msg; return; }
- page.toList();
+ page.owner.update(id, titleField.text, bodyEdit.text, (ok, msg) => {
+ if (!ok) { page.busy = false; page.owner.lastError = msg; return; }
+ page.owner.commitPush(kind, id, (ok2, msg2) => {
+ page.busy = false;
+ if (!ok2) { page.owner.lastError = msg2; return; }
+ page.toList();
+ });
});
return;
}
diff --git a/desktop/modules/notes/README.md b/desktop/modules/notes/README.md
index 0f642b8..42f989f 100644
--- a/desktop/modules/notes/README.md
+++ b/desktop/modules/notes/README.md
@@ -14,9 +14,9 @@ per note, named by its creation time in epoch seconds.
lists, links, inline code and quotes render; tables and fenced-code
highlighting do not.
- **Save** is two clicks: the first writes the note, the second reads
- **Commit & push?** and pushes it. **Delete** asks first, then removes the
- note and pushes the removal. With git off there is no second half, so Save
- writes and returns in one.
+ **Commit & push?**, writes whatever you have typed since, and pushes it.
+ **Delete** asks first, then removes the note and pushes the removal. With git
+ off there is no second half, so Save writes and returns in one.
- Leaving the editor with **Cancel**, the back arrow, Escape or a click
outside drops unsaved text. The save is the explicit act.
- **Update list** runs `git pull` and stamps `git.lastpull`.
@@ -38,9 +38,12 @@ git to resolve instead. A change made on two machines at once conflicts here
for the same reason it conflicts for `notes.sh`: every machine appends to the
one `db.json`.
-A write that fails is reported rather than assumed: the writes block, so a
-refused or failing `setText` is known when it returns, and a failed commit or
-push shows git's own message.
+A write that fails is reported rather than assumed. 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 and reports no error, which once put an empty note 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 failed commit or push shows git's own message.
**Git order.** `notes.sh` pulls before it writes and commits after, and the
page keeps that order: creating a note pulls first, then writes on what the