Files
mnote/design/03-rust-web/done/3-5-rust-web-main-execution-plane-cutover-plan-v1.md
T
lix-2026 5f97800489 chore: align local-first control plane and editor fixes
- wire SQLite control-plane access/session paths into Rust web local-folder routes

- preserve local Markdown attachment semantics across upload, reload, and secondary-pane resource tabs

- refresh design governance docs, Reasonix task templates, and bug records

- retire root .mcp.json local MCP config
2026-05-23 23:38:42 +08:00

21 KiB
Raw Blame History

3-5 [done] Rust Web 主 Web 执行面迁移实施计划 v1

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal:mnote-web / Rust family 成为主 Web 执行面,3000 继续作为唯一公开入口,但不再由 Next App Router / React 旧壳承担产品主执行语义。

Architecture: 采用“Rust Web gateway + server-first Rust shell + islands + legacy Next compat”分层。mnote-web 先接管公开入口、runtime config、auth/session、页面 shell、API transport 与 realtime streamNext App Router 降为内部 compat/legacy adapter,最终只在显式 legacy/debug 开关下启动。

Tech Stack: Rust workspace、mnote-webaxum、Leptos/WASM islands、Convex substrate、Next App Router legacy compat、Vitest、Playwright smoke、Cargo tests。


1. 当前基线

树域 Sidebar / 页面树 / 文件树 / picker 已完成 Rust family 默认执行面切换,但这不等于主 Web 执行面已经迁移完成。当前仍成立的事实是:

  • 3000 是唯一公开入口。
  • wolai-frontend/src/app 仍是 Next App Router 主应用入口。
  • mnote-web 已具备 documents、tree、kernel、stream、bridge、debug shell 等 route,但默认不是公开主站进程。
  • scripts/desktop-hot.js 默认启动 pnpm dev 作为前端 3000,并启动 FastAPI 8000;没有默认启动 Rust Web gateway。
  • 3104 已退役为显式 debug/internal 边界,不能把“恢复 3104 双入口”当作迁移方案。

因此,本计划的完成判定不是“某个 route 可从 Rust 调用”,而是:

主站公开入口仍是 3000,但 3000 背后的主执行 owner 从 Next App Router 迁到 mnote-web

2. 边界原则

  • 3000 保持唯一公开入口,不新增长期公开端口。
  • mnote-web 成为主 gateway、主 SSR shell、主 API transport、主 realtime transport 的 owner。
  • Next App Router 只能保留为内部 compat,负责尚未迁完的 legacy 页面或 React island bundle,不再新增长期业务语义。
  • Convex 保留为历史迁移源、显式 cloud source / compat / sync replica 边界;主 Web Rust 化后默认 storage / realtime / control-plane 不再依赖 Convex。
  • 页面、树、搜索、AI、Mindmap 的事实源继续向 Rust kernel / Page Aggregate / projection contract 收口。
  • BlockNote 保留为 fallback / 对照链,不作为主执行面迁移的阻塞项。

3. 目标文件结构

Rust Web

  • rust/crates/mnote-web/src/app.rs
    • 扩展主 gateway 配置、公开入口配置、legacy Next upstream 配置。
  • rust/crates/mnote-web/src/routes/mod.rs
    • 注册主页面 shell、runtime config、auth/session、API transport、stream route。
  • rust/crates/mnote-web/src/routes/web_shell.rs
    • 新建主 Web shell route//auth/documents/:id、必要的 workspace shell。
  • rust/crates/mnote-web/src/routes/session.rs
    • 新建 Rust-owned session / runtime config / token handoff route。
  • rust/crates/mnote-web/src/routes/gateway.rs
    • 新建 legacy Next compat proxy,只代理尚未迁移的路径。
  • rust/crates/mnote-web/src/routes/search.rs
    • 新建 server-first search shell 和 Rust-owned search transport。
  • rust/crates/mnote-web/src/routes/mindmap_shell.rs
    • 新建独立对象页 shell,先承接轻壳与 island mount。

Frontend Legacy Compat

  • wolai-frontend/src/lib/rust-web-main-execution-boundary.ts
    • 新建主执行面边界 manifest,固定 route owner、legacy owner、forbidden Next semantics。
  • wolai-frontend/src/lib/rust-web-main-execution-boundary.test.ts
    • 新建负向门禁,阻止把 Next App Router 再写成主执行 owner。
  • wolai-frontend/src/app/api/**
    • 逐步变成 thin proxy / compat adapter;新增业务语义必须失败。
  • wolai-frontend/src/app/(app)/**
    • 逐步从主入口降级为 legacy compat shell。
  • wolai-frontend/src/components/**
    • 可继续提供 islands 和重交互组件,但不能再拥有 page/tree/search/AI 的系统事实源。

Scripts / Smoke

  • scripts/desktop-hot.js
    • 改为默认启动 Rust Web gateway 作为 3000 ownerNext dev 只在内部 legacy port 启动。
  • scripts/desktop-hot.test.js
    • 覆盖 Rust gateway、legacy Next upstream、端口释放、跳过 legacy 的组合。
  • scripts/task114-rust-web-gateway-entry-smoke.js
    • 新建公开入口 smoke,证明 3000 由 Rust gateway 服务。
  • scripts/task115-rust-web-document-shell-smoke.js
    • 新建文档页 Rust shell smoke。
  • scripts/task116-rust-web-islands-smoke.js
    • 新建 search / AI / Mindmap islands smoke。
  • scripts/task117-next-retirement-guard.js
    • 新建最终退役门禁,证明主 smoke 不依赖 Next dev server。

Design

  • design/03-rust-web/process/3-6-rust-web-main-execution-plane-checklist-v1.md
    • 作为本计划的执行 checklist 和 harness task 来源。
  • design/03-rust-web/done/*
    • 只有当 checklist 全部完成并验证后,才移动完成项。

4. 非目标

  • 不恢复 3104 作为公开入口。
  • 不在本阶段强制删除 Convex。
  • 不要求一次性重写所有 React 组件;重交互可以作为 island/legacy bundle 保留。
  • 不把 BlockNote 的最终删除作为主 Web 执行面迁移的完成前提。
  • 不把“Rust kernel 拥有业务语义”误写成“Rust Web 已拥有主 Web 执行面”。

5. 实施任务

Task W-0:冻结主执行面 owner manifest 与负向门禁

Files:

  • Create: wolai-frontend/src/lib/rust-web-main-execution-boundary.ts

  • Create: wolai-frontend/src/lib/rust-web-main-execution-boundary.test.ts

  • Modify: design/03-rust-web/process/3-6-rust-web-main-execution-plane-checklist-v1.md

  • Modify: ARCHITECTURE.md

  • Step 1: 写主执行面 manifest

export const RUST_WEB_MAIN_EXECUTION_BOUNDARY = {
  schema: "mnote.rust_web.main_execution_boundary.v1",
  publicEntry: "3000",
  mainWebOwner: "mnote-web",
  legacyCompatOwner: "next-app-router",
  rustOwnedPlanes: [
    "web_gateway",
    "runtime_config",
    "auth_session_handoff",
    "document_shell",
    "page_aggregate_transport",
    "tree_projection_transport",
    "tree_command_transport",
    "tree_realtime_stream",
    "search_shell",
    "ai_bridge_transport",
    "mindmap_object_shell",
  ],
  nextAllowedDuties: [
    "legacy_react_bundle",
    "legacy_app_router_compat",
    "island_asset_source_until_migrated",
  ],
  forbiddenNextSemantics: [
    "主 Web gateway owner",
    "页面事实源",
    "树排序 canonical plan",
    "Page Aggregate command family owner",
    "tree realtime event contract owner",
    "搜索事实源",
    "AI tool protocol owner",
  ],
} as const;
  • Step 2: 写负向测试
import { describe, expect, it } from "vitest";
import { RUST_WEB_MAIN_EXECUTION_BOUNDARY } from "./rust-web-main-execution-boundary";

describe("RUST_WEB_MAIN_EXECUTION_BOUNDARY", () => {
  it("固定 3000 公开入口由 mnote-web 拥有,Next 只能作为 legacy compat", () => {
    expect(RUST_WEB_MAIN_EXECUTION_BOUNDARY.publicEntry).toBe("3000");
    expect(RUST_WEB_MAIN_EXECUTION_BOUNDARY.mainWebOwner).toBe("mnote-web");
    expect(RUST_WEB_MAIN_EXECUTION_BOUNDARY.legacyCompatOwner).toBe("next-app-router");
    expect(RUST_WEB_MAIN_EXECUTION_BOUNDARY.nextAllowedDuties).not.toContain("main_web_owner");
    expect(RUST_WEB_MAIN_EXECUTION_BOUNDARY.forbiddenNextSemantics).toContain("主 Web gateway owner");
  });
});
  • Step 3: 更新架构文档口径

ARCHITECTURE.md 的 Rust Web 状态改成:

mnote-web 已成为主 Web 执行面 ownerNext App Router 降为 legacy compat / island bundle source3-5 / 3-6 收口项已由 task-019..task-026 验证完成。

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/lib/rust-web-main-execution-boundary.test.ts src/lib/runtime-config.test.ts
cd /mnt/Data1T/mnote
git diff --check -- ARCHITECTURE.md design/03-rust-web wolai-frontend/src/lib

Expected:

  • Vitest 通过。
  • git diff --check 无输出。

Task W-1:让 mnote-web 默认成为 3000 gateway owner

Files:

  • Modify: rust/crates/mnote-web/src/app.rs

  • Modify: rust/crates/mnote-web/src/routes/mod.rs

  • Create: rust/crates/mnote-web/src/routes/gateway.rs

  • Modify: scripts/desktop-hot.js

  • Modify: scripts/desktop-hot.test.js

  • Create: scripts/task114-rust-web-gateway-entry-smoke.js

  • Step 1: 扩展 Rust gateway 配置

新增配置项:

pub public_bind_addr: String,
pub legacy_next_base_url: Option<String>,
pub enable_legacy_next_compat: bool,

默认值:

  • MNOTE_WEB_PUBLIC_BIND 默认 127.0.0.1:3000

  • MNOTE_WEB_LEGACY_NEXT_BASE_URL 默认 http://127.0.0.1:3100

  • MNOTE_WEB_ENABLE_LEGACY_NEXT_COMPAT 默认 true

  • Step 2: 新建 legacy gateway route

gateway.rs 只允许代理尚未迁移的 legacy 页面和 asset,不允许承接新业务语义。代理响应必须加:

x-mnote-web-owner: mnote-web
x-mnote-legacy-upstream: next-app-router
  • Step 3: 修改 desktop-hot 启动链

默认启动顺序改为:

  1. 释放 3000 / 3100 / 8000
  2. 启动 Next dev 到 127.0.0.1:3100,只作为 legacy upstream。
  3. 启动 mnote-web127.0.0.1:3000
  4. 启动 FastAPI 8000。

保留环境变量:

  • MNOTE_WEB_SKIP_GATEWAY=1:临时恢复旧 Next 3000,仅用于紧急回退。

  • NEXT_LEGACY_PORT=3100:覆盖 legacy upstream 端口。

  • Step 4: 写 smoke

task114 必须断言:

  • GET http://127.0.0.1:3000/health 或等价 gateway health 返回 Rust owner header。

  • GET http://127.0.0.1:3000/auth 可进入。

  • 主请求响应头含 x-mnote-web-owner: mnote-web

  • 未显式设置 legacy smoke 时不依赖 3104

  • Step 5: 验证

Run:

node --test /mnt/Data1T/mnote/scripts/desktop-hot.test.js
cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web gateway
cd /mnt/Data1T/mnote
node scripts/task114-rust-web-gateway-entry-smoke.js

Expected:

  • desktop-hot 测试证明默认 3000 owner 是 Rust gateway。
  • Cargo gateway 测试通过。
  • Smoke 证明 3000 主入口不再直接依赖 Next dev owner。

Task W-2:把 runtime config 与 auth/session handoff 迁到 Rust Web

Files:

  • Create: rust/crates/mnote-web/src/routes/session.rs

  • Modify: rust/crates/mnote-web/src/routes/mod.rs

  • Modify: wolai-frontend/src/lib/runtime-config.ts

  • Modify: wolai-frontend/src/lib/runtime-config.test.ts

  • Modify: wolai-frontend/src/app/api/auth/mnote-web-token/route.ts

  • Step 1: Rust Web 输出运行时配置

新增:

GET /api/runtime/config
GET /api/auth/session
POST /api/auth/session/refresh

响应必须只暴露浏览器安全字段,禁止暴露 CONVEX_SELF_HOSTED_ADMIN_KEY、内部 MNOTE_WEB_*_BASE_URL、legacy upstream secret。

  • Step 2: 前端 runtime config 改为消费 Rust-owned contract

浏览器侧继续只读同源 /api/runtime/config,不再从 NEXT_PUBLIC_MNOTE_WEB_BASE_URL 等 legacy env 兜底恢复 internal-only 边界。

  • Step 3: 保留 Next auth route 为 compat

/api/auth/mnote-web-token 只做兼容转发或测试入口,文档中标记为 legacy debug,不再作为主 session owner。

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web session runtime_config
cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/lib/runtime-config.test.ts src/app/api/auth/mnote-web-token/route.test.ts

Expected:

  • Rust Web session/config 测试通过。
  • 前端 runtime config 负向测试仍证明 internal mnote-web URL 不会泄漏回浏览器。

Task W-3:建立 Rust-owned 文档页 / Page Aggregate server-first shell

Files:

  • Create: rust/crates/mnote-web/src/routes/web_shell.rs

  • Modify: rust/crates/mnote-web/src/routes/documents.rs

  • Modify: rust/crates/mnote-web/src/routes/mod.rs

  • Modify: wolai-frontend/src/components/editor/document-shell.tsx

  • Modify: wolai-frontend/src/components/editor/document-content.tsx

  • Create: scripts/task115-rust-web-document-shell-smoke.js

  • Step 1: Rust Web 输出文档 server-first shell

新增:

GET /documents/:id
GET /api/page-aggregate/:id

/documents/:id 首屏必须由 Rust shell 决定 page aggregate snapshot、document read shell、editor island mount 信息。

  • Step 2: Next 文档页降级为 legacy compat

wolai-frontend/src/app/(app)/documents/[id]/page.tsx 只能继续服务 legacy upstream;新语义必须从 Rust shell 进入。

  • Step 3: 写文档 shell smoke

task115 必须断言:

  • GET /documents/<id> 响应头含 x-mnote-web-owner: mnote-web

  • 首屏 HTML 包含 page aggregate snapshot 标识。

  • 默认主编辑器仍是 leptos_tiptap_island

  • 不挂载 mnote-web-document-shell iframe。

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web document_shell page_aggregate
cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/components/editor/document-content.test.ts src/lib/documents/page-aggregate-builder.test.ts
cd /mnt/Data1T/mnote
node scripts/task115-rust-web-document-shell-smoke.js

Expected:

  • Rust 文档 shell contract 测试通过。
  • 前端文档组件继续作为 island / legacy component 可用。
  • Smoke 证明默认文档页入口由 Rust Web shell 服务。

Task W-4:把主 API transport 从 Next route 收薄为 Rust Web owned

Files:

  • Modify: rust/crates/mnote-web/src/routes/documents.rs

  • Modify: rust/crates/mnote-web/src/routes/tree.rs

  • Create: rust/crates/mnote-web/src/routes/search.rs

  • Modify: wolai-frontend/src/lib/tree-route-boundary.ts

  • Modify: wolai-frontend/src/app/api/documents/route-adapters.test.ts

  • Modify: wolai-frontend/src/lib/search/request.test.ts

  • Step 1: Rust Web 承接 documents / tree / search 主 transport

Rust Web route owner 至少覆盖:

  • /api/documents/meta

  • /api/documents/content

  • /api/documents/save

  • /api/tree/commands

  • /api/tree/projections/sidebar

  • /api/tree/projections/page

  • /api/tree/projections/file

  • /api/search/documents

  • Step 2: Next route 只保留 thin proxy / compat

Next route 可以做 cookie/header 透传、同源兼容和 legacy 测试,但不得再新增:

  • canonical tree sort

  • Page Aggregate payload 组装语义

  • search result shape owner

  • AI tool protocol owner

  • Step 3: 更新 route boundary manifest

TREE_3000_ROUTE_BOUNDARY_MANIFEST 继续保留 3000 public entry,但 owner 从 next-3000 改为 rust-web-gatewayNext duties 改为 legacy_thin_proxy

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web documents_api tree_commands search
cargo test -p bridge-runtime page_aggregate tree_subtree_move_command
cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/lib/tree-route-boundary.test.ts src/app/api/documents/route-adapters.test.ts src/lib/search/request.test.ts

Expected:

  • Rust Web 主 transport 测试通过。
  • Next route boundary 测试证明 Next 只保留 thin proxy / compat duties。

Task W-5:把 tree realtime stream 固定为 Rust Web 主链

Files:

  • Modify: rust/crates/mnote-web/src/routes/sse.rs

  • Modify: rust/crates/mnote-web/src/routes/stream_support.rs

  • Modify: wolai-frontend/src/app/api/mnote-web/stream/route.ts

  • Modify: wolai-frontend/src/lib/tree-stream/protocol.ts

  • Modify: wolai-frontend/src/lib/tree-stream/tree-stream.test.ts

  • Modify: wolai-frontend/src/lib/tree-stream/use-sidebar-tree-stream.test.tsx

  • Step 1: Rust Web 输出正式 snapshot + delta stream

同源主入口:

GET /api/tree/events?workspaceId=...&rootNodeId=...

事件必须覆盖:

  • snapshot

  • delta

  • resync

  • heartbeat

  • Step 2: 前端 stream consumer 改为 Rust-owned endpoint

/api/mnote-web/stream 降级为 compat alias;正式 consumer 使用 /api/tree/events

  • Step 3: 删除 freshness 补偿式主语义

PreferredSidebarSnapshotProvider 可以保留 fallback,但不能把 query/refetch freshness 作为正式实时主链的事实 owner。

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web tree_realtime stream
cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/app/api/mnote-web/stream/route.test.ts src/lib/tree-stream/tree-stream.test.ts src/lib/tree-stream/use-sidebar-tree-stream.test.tsx
cd /mnt/Data1T/mnote
node scripts/task112-tree-rust-family-regression-smoke.js

Expected:

  • Rust Web stream contract 测试通过。
  • Sidebar / page tree / file tree consumer 使用正式 Rust-owned stream。
  • /api/mnote-web/stream 只作为 compat alias。

Task W-6:把 Search / AI / Mindmap 页面壳迁到 Rust Web shell + islands

Files:

  • Create: rust/crates/mnote-web/src/routes/search.rs

  • Create: rust/crates/mnote-web/src/routes/mindmap_shell.rs

  • Modify: rust/crates/mnote-web/src/routes/hermes.rs

  • Modify: wolai-frontend/src/components/search/SearchPaletteHost.tsx

  • Modify: wolai-frontend/src/components/editor/DocumentAiAgentPanel.runtime.tsx

  • Modify: wolai-frontend/src/app/mindmap/[docId]/[mindmapId]/page.tsx

  • Create: scripts/task116-rust-web-islands-smoke.js

  • Step 1: Search 建立 server-first shell

GET /search 由 Rust Web 输出首屏和初始结果 contractReact search palette 只保留热键打开和交互 island。

  • Step 2: AI 面板收口为 bridge island

Rust Web / Hermes route 拥有 session、tool event、client action contract;前端 runtime 只负责展示、输入和 tool result 回传。

  • Step 3: Mindmap 独立页成为 Rust object shell

GET /mindmap/:docId/:mindmapId 由 Rust Web 输出对象页壳、initial projection 与 island mount 配置;Next page 降为 legacy compat。

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote/rust
cargo test -p mnote-web search_shell ai_bridge mindmap_shell
cd /mnt/Data1T/mnote/wolai-frontend
pnpm vitest run src/components/editor/DocumentAiAgentPanel.runtime.test.tsx src/lib/ai-agent/runtime/runAgent.test.ts src/lib/search/request.test.ts
cd /mnt/Data1T/mnote
node scripts/task116-rust-web-islands-smoke.js

Expected:

  • Search / AI / Mindmap 的页面壳 owner 是 Rust Web。
  • React runtime 只作为 island 或 legacy compat 存在。

Task W-7:退役 Next App Router 主入口,完成文档收口

Files:

  • Modify: ARCHITECTURE.md

  • Modify: design/03-rust-web/reference/3-1-rust-web-long-term-checklist-v2.md

  • Modify: design/03-rust-web/process/3-6-rust-web-main-execution-plane-checklist-v1.md

  • Move when complete: design/03-rust-web/process/3-5-rust-web-main-execution-plane-cutover-plan-v1.md

  • Move when complete: design/03-rust-web/process/3-6-rust-web-main-execution-plane-checklist-v1.md

  • Create: scripts/task117-next-retirement-guard.js

  • Step 1: 写最终退役 guard

task117 必须证明:

  • SKIP_NEXT_LEGACY=1 时,/auth/documents/:id、tree realtime、search shell 的主 smoke 仍可通过。

  • 未显式 legacy/debug 时不会启动 pnpm dev 作为 3000 owner。

  • 3104 不作为公开入口。

  • Next App Router 文档中标记为 legacy compat,而不是主入口。

  • Step 2: 更新 checklist 勾选状态

当 W-0 到 W-6 全部完成后,把 checklist 中所有完成项勾选,并将仍未迁移的 React islands 明确列为“允许保留的交互 island”,不能算主 Web 执行面 blocker。

  • Step 3: 移动完成文档到 done

只有 task117 通过后,才能把:

  • 3-5-rust-web-main-execution-plane-cutover-plan-v1.md
  • 3-6-rust-web-main-execution-plane-checklist-v1.md

移动到:

  • design/03-rust-web/done/

  • Step 4: 验证

Run:

cd /mnt/Data1T/mnote
node scripts/task097-homepage-entry-smoke.js
node scripts/task114-rust-web-gateway-entry-smoke.js
node scripts/task115-rust-web-document-shell-smoke.js
node scripts/task116-rust-web-islands-smoke.js
node scripts/task117-next-retirement-guard.js
git diff --check -- ARCHITECTURE.md design/03-rust-web scripts wolai-frontend rust/crates/mnote-web

Expected:

  • 主入口 smoke 不依赖 Next 作为 3000 owner。
  • 文档已把 Next App Router 从“当前主入口”改为“legacy compat”。
  • design/03-rust-web/process 不再保留已经完成的主执行面收口项。

6. 完成判定

本计划完成时必须同时满足:

  • 3000 仍是唯一公开入口。
  • mnote-web 是 3000 背后的默认 owner。
  • Next App Router 不再作为主 Web 执行面,只保留 legacy compat / island bundle / debug。
  • 文档页、tree realtime、search shell、AI bridge、Mindmap object shell 的 owner 已迁到 Rust Web。
  • SKIP_NEXT_LEGACY=1 的主 smoke 能覆盖核心入口。
  • design/03-rust-web/process 中本计划和 checklist 已移动到 done/

7. 自检

  • Spec coverage:覆盖 gateway、runtime config、auth/session、document shell、API transport、tree realtime、Search/AI/Mindmap islands、Next retirement。
  • Placeholder scan:本文不使用 TBD 或未定义的“后续处理”作为验收条件。
  • Type consistency:统一使用 mnote-webrust-web-gatewaynext-app-router legacy compat 三个 owner 名称。
  • Scope check:不要求删除 Convex,不要求一次性删除所有 React island,不恢复 3104 双入口。