aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--desktop/modules/notes/NotesPage.qml36
-rw-r--r--desktop/modules/notes/README.md5
-rw-r--r--docs/superpowers/specs/2026-10-16-notes-design.md6
3 files changed, 29 insertions, 18 deletions
diff --git a/desktop/modules/notes/NotesPage.qml b/desktop/modules/notes/NotesPage.qml
index 88382fc..585fcfb 100644
--- a/desktop/modules/notes/NotesPage.qml
+++ b/desktop/modules/notes/NotesPage.qml
@@ -37,10 +37,13 @@ Item {
property string bodyError: ""
// A write or git command is in flight; the buttons stand down meanwhile.
property bool busy: false
- // "create" | "edit" | "delete" once that write has landed and the commit
- // and push are still owed. It outlives any timer, so a click after a pause
- // pushes the note that was already written instead of writing it again.
+ // "create" | "edit" once that write has landed and the commit and push are
+ // still owed. It outlives any timer, so a click after a pause pushes the
+ // note that was already written instead of writing it again.
property string awaiting: ""
+ // Delete is confirmed before anything is destroyed: the first click only
+ // arms the button, the second removes and pushes.
+ property bool confirmDelete: false
// The shared Switch writes `checked` itself, which drops the binding
// above; StatusRow.qml documents the same and resyncs the same way.
@@ -50,6 +53,7 @@ Item {
page.bodyError = "";
page.preview = note !== null;
page.awaiting = "";
+ page.confirmDelete = false;
titleField.text = note ? note.title : "";
// Clear before the assignment below: that assignment is what retargets
// the body FileView, and its load may land synchronously, so clearing
@@ -62,6 +66,7 @@ Item {
function toList() {
page.awaiting = "";
+ page.confirmDelete = false;
page.busy = false;
page.editing = null;
page.bodyError = "";
@@ -98,22 +103,24 @@ Item {
function remove() {
if (page.busy || page.editing === null) return;
const id = page.editing.id;
- if (page.awaiting === "delete") {
- page.busy = true;
- page.owner.commitPush("delete", id, (ok, msg) => {
- page.busy = false;
- if (!ok) { page.owner.lastError = msg; return; }
- page.toList();
- });
- return;
- }
+ // Nothing is destroyed until the second click: a note lost to one
+ // stray click is only recoverable out of git history by hand.
+ if (!page.confirmDelete) { page.confirmDelete = true; return; }
+ page.confirmDelete = false;
page.owner.lastError = "";
page.busy = true;
page.owner.remove(id, (ok, msg) => {
page.busy = false;
if (!ok) { page.owner.lastError = msg; return; }
if (!page.owner.gitEnabled) { page.toList(); return; }
- page.awaiting = "delete";
+ // The removal is confirmed, so the push follows it rather than
+ // asking for a third click.
+ page.busy = true;
+ page.owner.commitPush("delete", id, (ok2, msg2) => {
+ page.busy = false;
+ if (!ok2) page.owner.lastError = msg2;
+ page.toList();
+ });
});
}
@@ -290,7 +297,8 @@ Item {
visible: page.editing !== null
danger: true
enabled: !page.busy
- text: page.awaiting === "delete" ? "Confirm?" : "Delete"
+ text: page.busy ? "Deleting…"
+ : page.confirmDelete ? "Confirm?" : "Delete"
onClicked: page.remove()
}
diff --git a/desktop/modules/notes/README.md b/desktop/modules/notes/README.md
index 8efd7bc..0f642b8 100644
--- a/desktop/modules/notes/README.md
+++ b/desktop/modules/notes/README.md
@@ -14,8 +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** is two clicks the same way.
- With git off there is no second half, so Save writes and returns in one.
+ **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.
- 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`.
diff --git a/docs/superpowers/specs/2026-10-16-notes-design.md b/docs/superpowers/specs/2026-10-16-notes-design.md
index 53b9d2e..459b526 100644
--- a/docs/superpowers/specs/2026-10-16-notes-design.md
+++ b/docs/superpowers/specs/2026-10-16-notes-design.md
@@ -95,8 +95,10 @@ Opened by a row (existing note) or **New note**.
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.
-- **Cancel** returns to the list without saving; **Delete**, existing notes
- only, is two clicks the same way and pushes the removal.
+- **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
+ note lost to one stray click being recoverable only out of git by hand.
- **Unsaved text is discarded.** Leaving the editor with Cancel, the page's own
back arrow, Escape or a click outside the drawer drops whatever was typed and
not saved. `Page` has no veto over a back, and a drawer that destroys its page