feat(mnote-web): add local file read support to mnote.doc.fetch + WS push design doc

## mnote.doc.fetch: local file path support
- Detect local file paths (starting with `/` or `./`) and bypass Convex aggregate
- Read .md file directly via fs::read_to_string
- Return source: "local_fs" in response
- Support maxChars truncation for local files
- Online Convex path unchanged

## Design: WS push migration (3-14)
- New: design/03-rust-web/process/3-14-rust-web-tree-realtime-ws-push-v1.md
- Documents commit 9d8e361e WebSocket push migration rationale,
  architecture, and verification

## Updated design doc references
- 3-3: Mark SSE as fallback transport (WS push is primary)
- 08 checklist: Note WS push in Tree Realtime section
- AGENTS.md: Update tree/realtime references to reflect WS push

## Housekeeping
- desktop-hot.js: remove 3 stale log messages about disabled services
- page_ai_workflow.rs: fix 5 warnings (unused import, dead_code)
This commit is contained in:
lix-2026
2026-05-17 15:57:40 +08:00
parent 9d8e361e43
commit 3f43020603
7 changed files with 291 additions and 67 deletions
@@ -0,0 +1,100 @@
# 3-14 Rust Web Tree Realtime WS Push 替代 SSE 轮询 v1
> 创建时间:2026-05-17
> 状态:`done`
> 关联 commit`57ec8322 feat(mnote-web): replace SSE pollMs=1000 polling with WebSocket push for tree realtime events`
>
> 前置设计稿:
> - `design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md` — SSE 事件流原始设计(本稿覆盖其 realtime transport 部分)
> - `design/10-review/process/08-kernel-architecture-next-priority-review-and-checklist.md` — 3.4 Tree Realtime Live Cache checklist
## 1. 结论
**`/api/tree/events` 的 SSE 轮询已替换为 WebSocket 推送 + SSE 降级。** 所有 Convex mutation 完成后通过 `stream_delta_tx` broadcast channel 推送 deltaWS 客户端零 Convex 查询(idle 状态),SSE 客户端降级到 60s 安全网。Convex RSS 从 7.7G 降至正常水平,每页 idle 时 0 POST /api/query/min。
## 2. 问题
```
Convex backend RSS: 7.7G
POST /api/query/min: ~17/min
32h 内累计: 32K+ Convex queries
74% 来自 SSE pollMs=1000 轮询
```
SSE `/api/tree/events?pollMs=1000` 每 1 秒触发一次 `load_stream_overview()``execute_runtime_query_via_convex()` → Convex POST /api/query。无论 workspace 状态是否变化,每个 cycle 都产生一次 Convex 查询。两个浏览器 tab 开同一 workspace 时,查询量翻倍。
## 3. 方案
### 3.1 Server 端
| 组件 | 文件 | 改动 |
|------|------|------|
| **Broadcast channel** | `app.rs` | `AppState` 新增 `stream_delta_tx: broadcast::Sender<Value>`capacity 128 |
| **Command hook** | `command_support.rs` | `execute_runtime_command_via_convex_with_artifacts` 签名从 `(&AppConfig, ...)` 改为 `(&AppState, ...)`mutation 成功后 `stream_delta_tx.send({"kind":"command_committed", ...})` |
| **WS handler** | `ws.rs` | `handle_socket``tokio::select!` 订阅 `stream_delta_tx`mutation 后推送 delta 事件给所有 WS 客户端;客户端可发送 `{"type":"resync"}` 请求新鲜 snapshot |
| **SSE handler** | `sse.rs` | 同时订阅 `block_delta_tx``stream_delta_tx`broadcast 可用时进入 push-only 模式(250ms heartbeat,无 Convex query),poll 降级为 60s 安全网 |
| **Call site updates** | 17 处 | `documents.rs``mindmap_api.rs``resource_trash.rs``tree.rs``hermes_tools/{artifact,block,page}.rs` 全部 `state.config()``&state` |
### 3.2 Client 端
| 组件 | 文件 | 改动 |
|------|------|------|
| **Bootstrap config** | `layout.rs` | JSON 中默认 `transport: "convex-command-log-ws"` + `wsEndpoint: "/api/realtime/ws"` |
| **WS controller** | `layout.rs` (JS) | `TREE_LIVE_CONTROLLER_JS` 扩展 `startWithWebSocket()`:支持 `snapshot` / `delta` / `resync` / `lagged-hint` 事件 |
| **Auto-fallback** | `layout.rs` (JS) | WS 2s 内无法连接时自动回退到 SSE |
### 3.3 传输对比
| | SSE poll(旧) | WS push(新) |
|---|---|---|
| Convex 查询 | 每 1s 一次,idle 不降 | 0(仅 mutation 后 push delta |
| 延迟 | max 1s | 近实时(< 50ms |
| 连接 | HTTP long-poll | 单 WebSocket |
| Convex RSS 影响 | ~17 Q/min → 7.7G | 0 Q/min idle |
| 回退 | — | WS 不可用时降级到 SSE(60s 安全网) |
## 4. 对其他设计稿的影响
### 4.1 需更新口径的文档
| 文档 | 旧口径 | 新口径 |
|------|--------|--------|
| `3-3-rust-web-tree-realtime-event-stream-v1.md` | SSE `/api/tree/events?pollMs=1000` 是主 transport | SSE 降级为 fallbackWS `wss://.../api/realtime/ws` 是主 transport |
| `08-kernel-architecture-next-priority-review-and-checklist.md` | 3.4 中提到 `/api/tree/events` snapshot/delta/resync 覆盖 | 增加 WS push 验证项 |
| `AGENTS.md` | `3000` 主壳接入 Rust Web `/api/tree/events` SSE consumer | 更新为 WS consumerSSE 仅回退) |
| `4-24-resource-tree-filetree-pagetree-source-contract-checklist-v1.md` | transport 仍列 SSE | 增加 WS transport 列 |
| `4-27-resource-lifecycle-command-cutover-v1.md` | SSE delta 路由描述 | 更新为 WS push delta |
### 4.2 不受影响的文档
| 文档 | 原因 |
|------|------|
| `4-6-tree-command-protocol-cutover-stage2-v1.md` | 只涉及 command 协议,不涉及 transport |
| `05-editor-mainline` 系列 | 编辑器不依赖 tree transport |
| `07-ai` 系列 | AI 工具不依赖 tree transport |
## 5. 验证
```bash
# 编译 + 单测
cargo build --manifest-path rust/Cargo.toml -p mnote-web
cargo test --manifest-path rust/Cargo.toml -p mnote-web
# 浏览器 smoke
# 1. 打开 http://localhost:3000 → F12 Console
# 2. 检查:transport=convex-command-log-ws, status=connected
# 3. 等 2min → Convex 日志 0 新增 POST /api/query
# 4. 另一 tab 新建页面 → 首 tab 无需刷新,页面树/文件树自动更新
# 回归 smoke(双浏览器)
node scripts/task446-tree-rename-dual-browser-live-smoke.js
node scripts/task447-tree-move-order-dual-browser-live-smoke.js
node scripts/task448-tree-resync-recovery-dual-browser-smoke.js
node scripts/task449-tree-sse-reconnect-snapshot-recovery-smoke.js
```
## 6. 后续
- [ ] 完全移除 SSE poll 路径(当前仍保留 60s 安全网)
- [ ] WS 连接池管理(多 tab 共享一条 WS)
- [ ] Client 端 WS reconnect 指数退避
@@ -1,6 +1,7 @@
# 3-3 [process] Rust Web Tree Realtime Event Stream 方案 v1
> 更新时间:2026-05-16
> 更新时间:2026-05-17WS push 迁移后口径更新)
> 关联新设计稿:`design/03-rust-web/process/3-14-rust-web-tree-realtime-ws-push-v1.md`
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
@@ -59,7 +60,7 @@ Rust 在这里是:
Rust Web 负责:
- SSR 页面壳
- SSE / WS 主链
- **WS push 主链**`/api/realtime/ws`),SSE 降级为 fallback60s 安全网)
- workspace / subtree 订阅入口
- 把 Convex 订阅与 Rust domain event 连接起来
- 向前端输出稳定的 projection snapshot + delta stream
@@ -115,11 +116,12 @@ Rust Web 负责:
- 把底层 mutation 变化转成树域可消费的语义事件
- 维护 projection rebuild 与 delta 生成规则
### 4.3 Rust Web SSE/WS transport
### 4.3 Rust Web WS push transport(主)+ SSE fallback
这里负责:
- 暴露正式 `/api/tree/events` 或等价 stream 入口
- 暴露正式 `/api/realtime/ws`WebSocket push,主 chain
- `/api/tree/events` 保留为 SSE fallback60s 安全网),不可作为主要 transport 依赖
- 管理 workspace / subtree 订阅
- 发送 snapshot、delta、cursor、ack、resync 信号
@@ -258,18 +260,19 @@ Rust Web 负责:
- iframe/postMessage tree shell 不是正式 realtime 主链
## 9. 当前实现复核(2026-05-09
## 9. 当前实现复核(2026-05-17WS push 迁移后更新
这份方案继续留在 `process/`,因为 Rust Web transport 与当前 `3000` 主壳 live consumer 已落地,但 page subtree / filetree / preferred snapshot 仍未完全统一到同一条正式 live cache。
这份方案继续留在 `process/`,因为 page subtree / filetree / preferred snapshot 仍未完全统一到同一条正式 live cache。**SSE transport 已降级为 fallback,主 transport 已切换为 WebSocket push**(详见 `3-14`)。
### 9.1 已完成
- [x] Rust Web 已暴露正式 `/api/tree/events`,并返回 `x-mnote-web-owner: mnote-web``x-mnote-tree-stream-owner: rust-web`
- [x] `/api/tree/events` 已能输出 workspace / subtree snapshot。
- [x] Rust Web 已暴露正式 `/api/realtime/ws`WebSocket push 主链)与 `/api/tree/events`SSE fallback),返回 `x-mnote-web-owner: mnote-web``x-mnote-tree-stream-owner: rust-web`
- [x] `/api/tree/events` 已能输出 workspace / subtree snapshotSSE fallback 保留)
- [x] `stream_support.rs` 已有 cursor、delta、resync 的基础判定逻辑。
- [x] `/api/stream/events` 与 WebSocket snapshot / resync 骨架已存在
- [x] legacy React 侧已有 `useSidebarTreeStream``EventSource` consumer,并有协议 / delta 单测
- [x] 当前 `3000` Rust shell 已直接挂载 tree live `EventSource` consumer,并通过 `data-mnote-tree-live-applied` 应用 delta / resync
- [x] WS handler 用 `tokio::select!` 订阅 `stream_delta_tx` broadcast channelmutation 后推送 delta 给所有客户端
- [x] 2026-05-17 commit `57ec8322`SSE pollMs=1000 polling 替换为 WebSocket push + SSE fallback。Convex idle 查询从 17/min 降至 0/min
- [x] legacy React 侧已有 `useSidebarTreeStream``EventSource` consumer,并有协议 / delta 单测(SSE fallback 仍在用)
- [x] 当前 `3000` Rust shell 已直接挂载 tree live WebSocket consumer(默认 transport `convex-command-log-ws`),并通过 SSE fallback 的 `data-mnote-tree-live-applied` 应用 delta / resync。
- [x] `task112` / `task120` / `task123` 已覆盖 `/api/tree/events` snapshot、delta / resync 与 stream owner 可用性。
- [x] `task165` 已验证双 pane 不重复建立第二条 tree live stream。
- [x] 2026-05-16 复核确认 workspace snapshot 同时携带 `data.dataset.kernel_sidebar_projection``data.dataset.kernel_file_tree_projection``task123` 已断言临时页 `doc:<documentId>` file tree row 出现在 `/api/tree/events` snapshot 中。