feat: autosave drafts, and stop losing them on a switch

Only Ctrl-S and :w ever wrote. A closed laptop, a crashed tab or an
expired session took the draft with it, and clicking another entry in the
list silently discarded unsaved work without so much as a prompt.

Every change now goes to localStorage immediately and to the server two
and a half seconds after typing stops. Opening an entry that has a stash
newer than the server's copy restores it and says so, the way vim reports
a swap file; :e! and :q! discard it. Switching entries flushes the
outgoing draft first.

The save response carries the entry's excerpt, so a save patches its list
row in place instead of re-reading the whole list — which autosave would
otherwise have done every few seconds.
This commit is contained in:
nvrl committed 2026-09-05 09:10:04 +02:00
1 parent f42c0c6e4b
commit 54743346fe
6 files changed
+129 -13

No files matched your search

+6 -1
View File
@@ -28,6 +28,9 @@ pub struct Entry {
pub id: i64,
pub title: String,
pub body: String,
/// The same preview the list shows, so a client that has just saved can
/// patch its list row without re-reading the whole list.
pub excerpt: String,
pub created_at: i64,
pub updated_at: i64,
pub shared: bool,
@@ -252,10 +255,12 @@ async fn load(state: &AppState, id: i64) -> AppResult<Entry> {
.ok_or(AppError::NotFound)?;
let share_token: Option<String> = row.get("share_token");
let body: String = row.get("body");
Ok(Entry {
id: row.get("id"),
title: row.get("title"),
body: row.get("body"),
excerpt: excerpt(&body.chars().take(HEAD_CHARS).collect::<String>()),
body,
created_at: row.get("created_at"),
updated_at: row.get("updated_at"),
shared: share_token.is_some(),