草稿该存在哪:per-session 是怎么成为共识的

给聊天输入框做草稿持久化,第一个要回答的问题是:这份没发出去的文字,算谁的?全局一份,还是每个会话一份?

今天打开任何一个主流 IM 或 AI 对话产品,答案都是一样的:草稿跟着会话走。这个答案现在看起来像常识,但它是各家产品花了将近十年、踩过若干真实事故才收敛出来的。这篇文章把这段演变的关键节点摆出来,每个节点都有据可查。

起点:草稿解决的是"被打断"

2016 年 6 月 14 日,Telegram 在官方博客发布 Drafts 功能。博客给出的理由很直接:messenger 里有很多人抢你的注意力,你输入到一半的重要消息被打断,然后就忘了。

Telegram 第一版就做对了三件事,后来证明这三件事都是共识的组成部分:

  • 草稿按会话存,API 里的 DraftMessage 构造器从第一天起就是 peer 维度的;
  • 聊天列表里直接显示未完成的草稿,有草稿的会话自动置顶;
  • 草稿云端同步,手机上打了一半,电脑上接着打。

值得注意一个反面细节:同时代的多数 IM 根本没有草稿。VentureBeat 当时的报道特意提了一句,这个功能"在很多聊天应用里都没有"。Telegram 的方案激进在一开始就回答了归属问题:草稿属于某个会话,所以它出现在会话列表里,跟着会话跨设备走。

Slack:把草稿做成一个"区"

Slack 在 2019 年 6 月的官方博客介绍了 Drafts:写到一半没发的消息,会出现在频道侧边栏一个专门的 Drafts 区域。帮助文档里写得更清楚:开始写消息而不发送,它会自动保存为草稿;草稿按 channel、DM、thread 各自归属。

Slack 补上的那块拼图是可发现性的第二种形态。Telegram 用列表内标记加置顶,Slack 用一个独立的 Drafts 视图收编所有会话的草稿。两条路线后来都被广泛采用,Mattermost 甚至两个都做了。

Zulip 的反面教材:光"存下来"不够

开源社区里 Zulip 的草稿演变最能说明问题。Zulip 很早就有关闭输入框自动存草稿的机制,但存完只能点 Drafts 按钮去列表里找。2021 年 5 月,issue #18555 列出了这个设计的三个实害:

  1. 回到草稿要点好几下,而这恰恰是最常见的操作;
  2. 新用户根本不知道有草稿功能,以为自己写了一半的内容丢了,这是真实的用户反馈;
  3. 草稿"泄漏":用户没意识到自己有未完成的稿子,直接写新消息,草稿列表堆满废稿,有用的反而找不到。

issue 里有一句话点破了共识的存在:"其他聊天产品(Slack、Discord)默认把草稿恢复到输入框。"到 2021 年,"自动保存 + 默认恢复"已经是拿来当论据的既成事实,不需要再论证。

这个 issue 2024 年 4 月才关掉。Zulip 最终的行为是:输入框打开时,自动恢复当前会话最近编辑的那条草稿;清空文本即删除草稿。从"存下来就行"到"无感恢复",Zulip 走了三年。它还提供了一个粒度上的参照:Zulip 的会话是 stream + topic,草稿也就跟着细到 topic。粒度跟着产品对"会话"的定义走,这是 per-session 共识里容易被忽略的一条。

Mattermost:粒度、同步、汇总视图的开源样本

Mattermost 是开源实现里资料最全的样本,它的代码和 PR 记录能看清共识落地的完整路径。

粒度上,Mattermost 的草稿 key 分两种:draft_{channelId} 存频道输入框,comment_draft_{rootId} 存线程回复框。和 Zulip 的 topic 一个道理:草稿挂在"消息将被发往的目标"上,线程也是一个目标。

持久化上,2021 年 7 月的 commit MM-37048 处理了一个真实 bug:草稿从 Redux persist 自动 rehydrate 改成手动从 localStorage 恢复,修掉"输入完立刻刷新页面丢草稿"的问题。同一改动里,组件在 beforeunload 和切换频道时都会先保存旧频道的草稿。

2022 年 11 月,PR #8869(+5540 行,113 个文件)合入了全局 Drafts 页,把所有频道和线程的草稿收进一个中央视图。到 v7.7(2023 年初),草稿默认同步到 Mattermost 服务器,web 和桌面端都能拿到,至此追平了 Telegram 2016 年的云同步。

这条时间线值得单独看:本地按会话存(更早)→ 修持久化可靠性(2021)→ 中央汇总视图(2022)→ 服务端同步(2023)。每步都有明确的用户问题在推动。

反例:共享草稿是一份事故报告

共识为什么是对的,反过来说更清楚。2026 年,meshmonitor 的 issue #4183 报告了一个事故:DM 视图和频道视图共享同一个 React state 当草稿,在私聊里写了一半切到频道,草稿还留在输入框里,发送就直接发去了公开频道。

修复 PR #4185 的描述里写着定性结论:"A private DM draft could even be sent to a public channel --- a real correctness/privacy bug."共享草稿不是体验瑕疵,是隐私事故。修复方式也符合共识:按会话标识(tab + 节点/频道)隔离草稿,目标变化时统一处理。

类似的问题在别的项目里以较轻的形态反复出现。alkem-io 的 PR #10089 修"关掉面板丢草稿",顺手修了两个连带 bug:草稿串到下一个会话(靠按会话 key 隔离解决)、发送失败时输入框已被清空(改成发送成功才清)。Freegle 的 PR #1031 记录的用户抱怨更朴素:"我在一个聊天里写回复,点去另一个聊天想复制粘贴点东西,回来草稿没了。"

这些报告的共性是:没有一份是抱怨"草稿为什么不能全局共享",全是抱怨草稿丢了、串了、发错地方了。共识是被这种抱怨喂大的。

AI 时代的新问题:会话还不存在的时候

AI 对话产品给这个老共识加了一个新场景:用户输入的时候,会话往往还没创建。草稿挂在哪?

OpenHands 的 use-draft-persistence.ts 是目前最完整的开源答案,它把场景拆得很细:

  • 有会话 ID,草稿存 localStorage,按会话 key;
  • 在首页(还没有会话),草稿存 sessionStorage 的一个固定 key,标签页关掉就丢,理由是没归宿的内容不值得长期留;
  • 新会话初始化用临时 task ID,草稿不能跟着丢,等真实会话 ID 就绪后把草稿迁移过去;
  • 保存前校验捕获的会话 ID 没过期,防止快速切换时把 A 的草稿写进 B。

first-tree 的 PR #1165 补充了两个工程细节:草稿 map 封顶最新 100 条做 LRU;只有附件没有文本的状态不持久化,避免系统自动带的默认选项伪装成用户草稿。

alkem-io 则贡献了另一条边界:草稿按 { [userId]: { [conversationId]: draft } } 嵌套,登出时整个清掉,因为草稿是私密消息内容;发送成功才删草稿,而不是按下发送就删,他们专门修过后者导致的静默丢内容。

收敛出来的五条

把这些节点叠在一起,共识可以拆成五条互相独立的判断,每条背后都有上面的事实支撑:

  1. 归属粒度跟着会话定义走。IM 是 channel/DM,Zulip 细到 topic,Mattermost 细到 thread,AI 产品是 conversation。全局共享一份草稿的方案在每一处都已被证伪。
  2. 自动保存,默认恢复。Zulip 的三年弯路证明,"存了但要用户自己记得去找"等于没存。
  3. 会话列表给出可见性。Telegram 的列表标记加置顶、Slack 的 Drafts 区、Mattermost 的全局 Drafts 页,形式不同,目的一样:草稿找得回来才有意义。
  4. 发送成功才清除。alkem-io 用实际 bug 确认过,按下发送就清空会在失败路径上静默丢内容。
  5. 无会话场景给固定归属,创建后迁移。这是 AI 产品新补的一条:home 页或新会话输入时挂固定 key,会话创建成功把草稿带过去。

回头看,这个共识没有过一次公开的"定义时刻",它是 Telegram 立下的原型、Slack 补齐的形态、Zulip 和 Mattermost 用开源进程反复验证、再由一串丢草稿的事故报告反向加固出来的。今天再给任何一个聊天输入框设计草稿,不按这五条做,需要的论证成本比按这五条做高得多。

参考来源

相关推荐
IPIPD43 分钟前
Proxifier规则为什么没有生效?从连接日志定位规则命中与DNS路径
前端
baiyunfan44 分钟前
微信小程序做 PDA 扫码出入库:库存并发控制与 4 个坑
前端
ZoeLandia44 分钟前
React6:组件通信
前端·react.js·typescript
爱勇宝44 分钟前
AI 都这么强了,咱们程序员以后还能干点啥?
前端·后端·程序员
FPGA小徐1 小时前
【一生一芯 / PA】AM 核心文件逐行解析(上):从 start.S 到 gpu.c
前端·算法
三十而立洋1 小时前
知枝 · MindSprout:可以直接将 markdown 转换为思维导图的 vscode 插件
前端·visual studio code
兔子零10241 小时前
架构师最危险的能力:用正确的技术,做出错误的架构决策
前端·javascript·后端
Bug收容所1 小时前
Vue+js Day2
前端·javascript·vue.js
掘金酱2 小时前
带着作品来,坐下来聊|掘金 X-lab · Coffee Talk 邀请你来见一面
前端·javascript·后端