# 3-17 [done][bug] SSE push 模式跳过 polling safety net v1

> 发现时间：2026-05-17
>
> 状态：`[done]`
>
> 关联主线：`03-rust-web`

## 1. 问题定义

SSE route 注释和架构口径暗示 polling 可作为 safety net，但实现中只要 `stream_delta_rx.is_some()`，循环就进入 push/heartbeat 分支并跳过 polling。

## 2. 证据

- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:35) 附近说明 SSE 事件路径。
- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:116) 附近在 `stream_delta_rx` 存在时走 heartbeat / push 分支。

## 3. 影响

- broadcast 丢失、进程重启、跨进程实例不共享 channel 时，SSE 连接可能无法靠 Convex bridgeLogs 追上。
- fallback 名义存在，但实际恢复能力不足。
- 客户端可能长期停在旧 snapshot。

## 4. 建议修复

- push 模式也应按 cursor 周期性检查 bridgeLogs，或在 heartbeat 周期中合并轻量 polling。
- 增加测试：模拟 `stream_delta_rx` 无消息但 bridgeLogs 有新事件，SSE 应产出 delta/resync。
- 明确 `pollMs` 与 push channel 同时存在时的优先级。

## 5. 修复

已修复：

- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:117) push 模式在 broadcast 静默时不再只发 heartbeat，而是按 `pollMs/maxPolls` 执行 bridgeLogs polling safety net。
- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:135) polling 检测到 `Delta` 时构建标准 stream delta payload 并发送 `delta`。
- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:157) polling 检测到 `Resync` 时回读 snapshot 并发送 `resync`。
- [sse.rs](/mnt/Data1T/mnote/rust/crates/mnote-web/src/routes/sse.rs:402) 增加回归测试，断言 push 模式下 `maxPolls=1` 会触发 safety-net polling 并正常结束。

## 6. 验证

```bash
cargo test --manifest-path rust/Cargo.toml -p mnote-web routes::sse::tests -- --nocapture
```

结果：5 个 SSE route 测试通过。
