auto-save drafts in PostForm (Notion/Coda-style)
Created by: jedmund
Stacked on #89.
Summary
exploratory PR retrying draft auto-save after the previous attempt was deleted in Feb 2026 (commits f227f295, 142d3d18) for being too complex (782 lines across 8 files: localStorage drafts, recovery prompts, offline retry queues, per-keystroke content stringification, etc).
this attempt is intentionally minimal. scope: posts only, drafts only.
what got built
-
src/lib/components/admin/forms/useAutoSave.svelte.ts— small Svelte 5 rune helper. single debounced timer (1500ms), one in-flight request at a time, queues at most one follow-up if changes pile up during a save. exposes{ state, flush() }wherestate ∈ {idle, unsaved, saving, saved, failed, conflict}. -
src/lib/components/admin/SaveStatus.svelte— small status pill:Saving…,Saved,Unsaved,Save failed(with retry),Conflict — reload(with reload link). -
src/lib/components/admin/StatusDropdown.svelte— added optionalhidePrimaryprop so PostForm can hide the "Save draft" button on drafts and let SaveStatus do the talking. -
src/lib/components/admin/forms/PostForm.svelte- replaced the per-keystroke
JSON.stringify(content)snapshot with O(1)editVersion/savedVersioninteger counters (the perf footgun blamed for the previous attempt) - wired
useAutoSavefor drafts only — published posts keep manual save (avoids re-triggering Bluesky/Mastodon syndication every 1.5s) - on draft posts: header shows
<SaveStatus />instead of the manual "Save draft" primary button - on published posts: UI unchanged
- 409 conflict handling — auto-save transitions to
conflictstate and stops; SaveStatus surfaces a "Reload" action. PostForm previously swallowed 409s silently. - flush on Cmd+S, on window blur, and on in-app navigation away from a dirty draft (no more UnsavedChangesModal popping for drafts — it just saves and lets you go)
- Publish button while a draft has pending edits → flushes auto-save first, then publishes on top of the latest saved snapshot
- replaced the per-keystroke
what auto-save does NOT do (lessons from the deleted attempt)
- no localStorage / draft recovery prompts
- no offline mode / retry queue
- no rollout to ProjectForm / GardenItemForm / AlbumForm in this PR — wait until PostForm proves itself
Test plan
-
new post: type a few words → after ~1.5s SaveStatus shows "Saved" and URL silently swaps to /admin/posts/[id]/edit. continue typing → "Unsaved" → "Saving…" → "Saved". no manual save click required. -
existing draft: edit a paragraph → auto-saves silently. refresh; changes persisted. -
Cmd+S during active editing: SaveStatus immediately shows "Saving…" then "Saved" (debounce flushed). -
Publish on a dirty draft: auto-save flushes first, then publish runs. UI switches to the published variant ("Save changes" / "Move to draft" dropdown). -
two-tab test: edit and save in tab A → tab B's next auto-save returns 409 → SaveStatus shows "Conflict — reload" with a working reload link, and stops auto-saving. -
published post: confirm NO auto-save fires when editing. existing manual save UI unchanged. -
navigate away from a dirty draft via sidebar / link: form auto-saves silently, then navigates. no UnsavedChangesModal. -
pnpm run checknet error count unchanged at 118.