A user told me the delete button in Plume's drafts list did nothing. No error, no console output — the × just sat there.
The button was fine. The handler ran. It returned early on its own guard.
Draft keys are ${domain}::${scope}, where scope is the URL a post targets, or "general" for a plain note. The composer built it like this:
const scope = state.bookmarkOf ?? state.inReplyTo ?? state.likeOf ?? state.repostOf ?? "general";
?? falls through on null and undefined — not on "". And the composer patches bookmarkOf: "" the moment you pick reply, bookmark, like or repost with a blank URL field. So those drafts were filed under example.com::, and the delete handler bailed:
const [domain, scope] = key.split("::", 2); if (!domain || !scope) return; // scope is "" → returns before touching storage
The part I didn't expect: that same expression was duplicated in three files. Save in Composer.tsx, restore and post-cleanup in popup/main.tsx. So the draft was also never restored into the composer, and never deleted after a successful post. Three symptoms, one operator.
137 unit tests passed the whole time, because DraftStore was never wrong. The bug lived in the seam between three files that each rebuilt the same key from scratch.
Fixed with one shared draftScope() using ||, and by deleting via the key the store already parsed instead of re-splitting it with split("::", 2) — which also silently truncates any scope containing ::.
Then an end-to-end test that seeds a localhost:: draft and clicks the ×. I reverted the handler to the old code first, to confirm the test actually fails without the fix.