交互反馈:0ms 规则#
用户点击后,只要操作耗时超过约 100ms,就必须立即提供可见反馈。客户端在 0ms 渲染乐观过渡状态;真实数据到达后替换;失败时回滚并显示错误。
网络请求等待期间,点击不能没有任何可见变化。
三个层次#
- 0ms 乐观状态:点击后,在任何网络 I/O 之前立即切换客户端可见状态,例如加载卡片、目标版本高亮、“正在停止…”、聊天记录中的待处理消息。直接写入 session store(消息或 store 对象上的乐观标志、状态补丁),不另建一套状态记录。
- 服务端快速确认:后端确认请求。函数运行时,dispatcher 在分发时预先创建运行节点,因此点击后约 0.13s 的
load_session会返回真实的待处理卡片,chat_ack {function_run:true}则立即触发 hydration。hydration 的setMessages替换整份聊天记录,使用临时 id 的客户端占位项随之移除,不产生闪烁。 - 流式更新:
tree_update/stream_event增量实时填充卡片,最终的result/running_task_clear完成状态更新。
失败回滚(必需):每个乐观状态都必须结束。真实数据替换它,或者超时(控制操作为 10s,OPTIMISTIC_TIMEOUT_MS)后恢复状态并显示错误提示。无限等待的乐观状态会误导用户,比没有反馈更差。
共享辅助函数#
apps/web/lib/runtime-bridge/optimistic-action.ts 提供 optimisticAction({apply, settled, revert, onTimeoutMessage})。apply 设置 0ms 状态;真实数据替代后,settled() 返回 true,例如消息已从 store 移除、树已重新填充、branch.active 已切换。超时时若 settled() 仍为 false,则运行 revert 并显示提示。确认过程通过 load_session 重新加载的界面使用它,例如重试和版本切换。纯本地短暂状态(停止、fn-form 占位、分支切换)直接在对应代码中实现这一模式。
各界面的行为#
| 界面 | 点击后的前 100ms |
|---|---|
| 聊天发送 | 欢迎页在 0ms 隐藏;chat_ack 到达时加入用户消息和回复占位项(约一次网络往返) |
| 停止按钮 | 0ms 清除 runningTask,并把助手消息修改为 [cancelled by user];同时处理发送队列 |
| 函数调用重试 | 0ms 将卡片正文改为加载状态并显示“运行中”,切换器变为 N+1/N+1;重新加载后填充;10s 后回滚 |
| fn-form / 欢迎页提交 | 0ms 插入待处理运行卡片;hydration 无缝替换;POST 失败则移除并提示 |
Runtime < N/M > 切换器 |
0ms 将当前卡片改为加载状态并显示目标同级索引;重新加载后替换内容;10s 后回滚 |
聊天消息 < N/M > 切换器 |
N/M 标签在 0ms 更新到目标;重新加载后填充;失败时恢复标签并提示 |
| 分支面板切换 | 0ms 高亮所点击的行为 active;真实 branch.active 替换;10s 后自行清除 |
| 聊天重试 / 编辑 / 分支 / 回退 | setBusy(true) 使按钮变暗,setRunActive 禁用编辑/重试;busy 标志就是过渡反馈 |
| 侧栏会话切换 | 调用 router.push;store 已有历史消息,因此路由切换时直接渲染缓存消息 |
| 启用/禁用模型、工具、开关 | 立即更新本地 store |
新增乐观状态使用 store 的既有机制:updateMessage 状态/树补丁、siblingIndex、setRunningTaskFor 和 appendMessage 占位项。不建立并行状态。
Last updated · 2026-10-10