34 KiB
5-28 Leptos Tiptap 编辑器 Runtime 模块拆分 v1
创建时间:2026-05-24 状态:
done触发背景:
bugs/0524.md第 4 条删除本地 Markdown 附件链接后Ctrl+Z/Ctrl+Y不可用,根因是编辑器删除路径绕过 Tiptap/ProseMirror history。- 修复过程中 CodeGraph 能索引 Rust 符号,但
rust/spikes/leptos-tiptap-spike/src/lib.rs单文件约一万行,职责过多,主控和 worker 都需要反复字面搜索。design/03-rust-web/done/3-19-rust-web-browser-runtime-module-extraction-v1.md解决 Rust Web shell/browser host runtime 外置化;本设计补齐编辑器主线拆分。
1. 结论
leptos-tiptap-spike/src/lib.rs 的问题不是 raw string,而是编辑器 runtime 仍以单个巨型 Rust 文件承载过多职责:Tiptap 初始化、命令执行、附件上传、附件链接增强、history、块菜单、DOM overlay、mindmap node view、page options、AI surface 都混在一起。CodeGraph 对这种文件只能给出符号位置,不能给出清晰模块边界。
后续不应继续在该文件中追加新的编辑器行为。应分阶段把编辑器命令、附件链接/上传、history 安全命令、DOM bridge/overlay、mindmap node view 拆入独立 Rust 模块;保持 leptos-tiptap island 仍是当前文档页主编辑器 runtime,但让每个行为域能被单独搜索、测试、派发给 worker。
2. 当前代码基线
-
rust/spikes/leptos-tiptap-spike/src/lib.rs- 约一万行。
- 同时包含 CSS、Tiptap extension 列表、slash menu、toolbar、block menu、table toolbar、attachment upload、attachment link handling、local folder path、history/undo、DOM selection、mindmap node view、runtime bridge。
- 最近第 4 条 bug 的关键函数和逻辑散落在同一文件中,例如附件链接识别、keydown 处理、
apply_html_update、sync_persisted_editor_command、delete_top_level_block_html。
-
reference-code/leptos-tiptap/- 提供底层 Tiptap Rust/JS bridge。
- 已有
TiptapEditorHandle、TiptapExtension、TiptapRange、delete_selection、delete_range、undo、redo等命令。 - MNote 不应把这些底层 bridge 再复制一遍,而应在 spike runtime 上方建立 MNote 编辑器行为模块。
3. 与 3-19 的边界
03-rust-web 负责:
- Rust Web SSR shell。
- document pane host adapter。
- resource tab / secondary pane / URL / conflict panel 生命周期。
- filetree / resource open / local upload 的 shell 侧适配。
05-editor-mainline 负责:
- Tiptap 编辑器内部命令。
- 编辑器内附件链接和上传块行为。
- undo/redo/history 安全边界。
- 编辑器 DOM selection / overlay / toolbar / block menu。
- Tiptap node view,例如 mindmap block。
交界规则:
- Shell 可以告诉 editor “打开某个文档/资源/保存状态/挂载 pane”。
- Editor 可以告诉 shell “内容变更/附件打开 intent/资源上传完成/冲突状态”。
- Shell 不应直接改 Tiptap document 内部结构。
- Editor 不应决定 workspace tree/resource 归属真相。
4. 目标
- 把
src/lib.rs从“巨型编辑器 runtime”收敛为组件入口和模块装配层。 - 让附件、history、命令、DOM bridge 等高频 bug 区域成为独立 Rust 模块。
- 修 bug 时能把 worker 限定到单一模块和单一 smoke。
- 统一编辑器命令合同:能用 Tiptap command/transaction 的行为,不再通过
set_content/HTML 整文替换实现用户编辑动作。 - 建立 history 安全基线:删除、插入、重排、格式转换、附件操作都要明确是否进入 undo stack。
5. 非目标
- 不替换 Tiptap 或
leptos-tiptapbridge。 - 不把文档事实源迁回前端。
- 不一次性重写编辑器 UI。
- 不在本设计中实现 Phase C 流式 apply / review session。
- 不把 Page AI panel 或 Hermes/Reasonix 工具链混入本次编辑器 runtime 拆分。
6. 目标模块布局
建议新增目录:
rust/spikes/leptos-tiptap-spike/src/
lib.rs
editor_runtime/
mod.rs
extensions.rs
command_sync.rs
history_safe_commands.rs
attachment_links.rs
attachment_upload.rs
local_markdown_paths.rs
dom_selection.rs
overlays.rs
block_menu.rs
table_toolbar.rs
mindmap_node_view.rs
bridge_events.rs
persistence.rs
第一阶段可以只拆高频问题模块,不强求一次建完所有文件。
7. 分阶段执行清单
归档说明(2026-05-25):本节保留原始分阶段路线,但不再作为当前 done/ 状态的未完成 checklist。已经满足归档条件的范围以第 13 节 Done Gate 为准;仍未实施或被明确划到 03-rust-web / 后续编辑器切片的条目改为普通状态项,后续需要另起设计或 bug/checklist 承接,不能在本 done 文档中继续以未勾选 checklist 表达。
P0:热点函数归属表和基线保护
- 已记录:当前
src/lib.rs中最近 bug 高频函数的归属模块:selected_local_attachment_link_in_editor_state->attachment_links.rsselected_local_attachment_link_text->attachment_links.rs/dom_selection.rssync_persisted_editor_command->command_sync.rsapply_html_update/apply_document_update->command_sync.rs,并标记 history 风险delete_top_level_block_html/reorder_top_level_block_html-> 待替换为history_safe_commands.rs- 2026-05-24:
delete_top_level_block_html已删除,块菜单删除改走delete_top_level_block_with_history(...)-> Tiptapdelete_range/insert_content_at。
- 2026-05-24:
- upload slash action / local asset path ->
attachment_upload.rs/local_markdown_paths.rs - toolbar/block menu DOM anchor ->
overlays.rs/block_menu.rs
- 后续:建立 history 风险清单;所有用户编辑动作中仍调用
set_content或 HTML 整文替换的路径必须登记。 - 已保留:当前已通过的
scripts/task488-local-attachment-link-delete-undo-smoke.js作为 RED/GREEN 基线。
P1:抽出附件链接和 history-safe delete
- 新增
editor_runtime/attachment_links.rs。 - 迁出本地附件链接识别:
- DOM selection fallback。
- Tiptap editor state link attributes 判断。
/api/local-folder/files/open链接识别。
- 新增
editor_runtime/history_safe_commands.rs。 - 将“选中附件链接文本后 Delete/Backspace”收口为命名函数,例如
delete_selected_attachment_link_with_history(...)。 - 验证
task488仍通过。
P2:抽出 command sync / persistence
- 新增
editor_runtime/command_sync.rs。 - 新增
editor_runtime/persistence.rs。 - 迁出:
read_editor_snapshotsync_editor_outputssync_persisted_editor_commandapply_document_update(当前保持零调用者现状)
- 后续:迁出 change/status event dispatch 的编辑器侧包装。
- 2026-05-24 复核:
apply_html_update仍依赖ChangePayload/ChangeMetaPayload、dispatch_change_event、dispatch_runtime_state、HoveredBlockState和 toolbar overlay state,暂留lib.rs,等待 P4bridge_events.rs/overlays.rs后再迁。
- 2026-05-24 复核:
- 明确
set_content只允许用于:- 初始加载
- host 替换文档
- 外部同步/恢复
- 非用户编辑的兼容兜底
- 用户编辑动作必须优先走 Tiptap command/transaction。
- 2026-05-24:块菜单删除已从
apply_html_update/set_content(TiptapContent::html(...))迁到history_safe_commands.rs的delete_top_level_block_with_history(...),并由scripts/task489-block-menu-delete-undo-smoke.js覆盖 Ctrl+Z/Ctrl+Y。
- 2026-05-24:块菜单删除已从
P3:抽出附件上传与本地 Markdown 路径
- 新增
editor_runtime/attachment_upload.rs。 - 不纳入本设计当前归档范围:新增
editor_runtime/local_markdown_paths.rs。- 2026-05-24 复核:
local_markdown_paths.rs不应落在leptos-tiptap内部;本地 Markdown 路径、rootUri、/api/local-folder/files/openURL 和上传落盘归03-rust-web的 shell host runtime /local-upload-runtime.js。
- 2026-05-24 复核:
- 迁出 slash 上传附件、图片的编辑器 intent。
dispatch_editor_upload_request(kind, accept)已迁入attachment_upload.rs。SlashActionKind::Image/UploadAttachment仍留在 slash menu 基础结构中,只调用 editor intent。
- 不纳入本设计当前归档范围:迁出 Office/PDF/code 文件的编辑器内插入逻辑。
- 当前实际插入链路在
layout.rs/local-upload-runtime.js,不是 editor runtime;后续由03-rust-web继续收口。
- 当前实际插入链路在
- 明确 editor 只负责发起上传 intent;资源保存位置和归属仍由 shell/Rust API 合同决定。
- 后续覆盖:
- 上传第一个/第二个附件后链接仍可点击。
- 刷新后链接不退化为普通外链。
- secondary pane 上传与 primary pane 隔离。
P4:抽出 DOM bridge / overlay / block menu
- 后续:新增
editor_runtime/dom_selection.rs、overlays.rs、block_menu.rs。 - 后续:迁出 selection、toolbar anchor、slash menu anchor、block menu anchor、image/table toolbar anchor。
- 后续:对所有 keydown/click 捕获建立“谁先处理、谁 stop propagation”的规则,避免 Tiptap 原生快捷键与外层 window listener 互相吞事件。
- 后续覆盖:
Ctrl+Z/Ctrl+Y- slash menu
- block menu
- table toolbar
- attachment context menu
P5:抽出 mindmap node view
- 后续:新增
editor_runtime/mindmap_node_view.rs。 - 后续:迁出 mindmap block 的 node view、runtime mount、projection refresh、simplemindmap event bridge。
- 后续:保持 mindmap 只是编辑器块视图,不成为正文事实源。
P6:收尾
- 后续:
src/lib.rs只保留:- module imports
- top-level component装配
- 少量常量和 view glue
- 后续:单个模块超过约 800 行时继续拆分。
- 已完成:运行 CodeGraph sync/index,确认新增 Rust 模块被索引。
- 后续:更新 worker skill/task template;编辑器 bug 默认先查
editor_runtime/*,再回到lib.rs。
8. History 安全规则
用户直接触发的编辑动作:
- 必须优先使用 Tiptap command/transaction。
- 如果临时必须使用
set_content,必须在 bug/design 中标明为什么不能走 command,并补一个 undo/redo 不支持或受限的说明。 - 新增编辑动作的 smoke 或单测应明确检查 undo/redo,除非该动作按产品定义不可撤销。
非用户编辑动作:
- 初始加载、host 替换文档、外部文件 watcher 重载、冲突恢复可以使用
set_content,但必须避免污染用户 undo stack。 - 外部同步应与 working copy 冲突模型结合,不能悄悄覆盖当前用户编辑。
9. Worker 协同规则
拆分后给 ClaudeCode / Reasonix 的任务应限定为:
允许读取/修改:
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/attachment_links.rs
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/history_safe_commands.rs
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/command_sync.rs
- rust/spikes/leptos-tiptap-spike/src/editor_runtime/persistence.rs
- scripts/task488-local-attachment-link-delete-undo-smoke.js
禁止:
- 修改 shell resource open 逻辑
- 修改 Rust tree command 语义
- 整文重写 src/lib.rs
采纳条件:
- worker handoff 明确命中哪个模块。
- diff 不跨越无关模块。
- 主控能运行对应 smoke。
- UI/浏览器行为必须有截图或 result.json 可审计证据。
10. 执行策略
本设计与 3-19 同步采用 Codex 主控、Reasonix 短跑 worker:
- Codex 负责编辑器主线边界、history 规则、最终 diff 复核和 smoke 验收。
- Reasonix 只处理短任务,不在一个 run 里同时做架构判断、代码迁移和浏览器验收。
- 同时最多 2 个 Reasonix worker;每批完成后必须先读取
process-handoff.md/json和 Hindsightreasonixrecall,再进入下一批。 - Batch 0 只读,不修改文件,用来生成
src/lib.rs热点函数归属表和set_content/ HTML 整文替换风险表。 - 实施批次必须优先放在
editor_runtime/*,避免继续向src/lib.rs追加大块逻辑。
当前建议批次:
- Batch 0 / R2:只读审计
rust/spikes/leptos-tiptap-spike/src/lib.rs中 history、set_content、附件链接删除、上传插入相关路径,输出函数归属表和风险等级。 - Batch 1:先抽出
attachment_links.rs和最小history_safe_commands.rs,保持task488通过;随后优先处理块菜单 delete 仍走 HTML 整文替换的 history 风险。 - Batch 2:抽出
command_sync.rs/persistence.rs,登记仍允许set_content的 host 替换和外部同步路径。 - Batch 3:抽出
attachment_upload.rs/local_markdown_paths.rs,覆盖第一个/第二个附件上传和点击打开 smoke。 - Batch 4:抽出 DOM selection / overlay / block menu,建立 keydown/click 捕获顺序规则。
Batch 0 验收标准:
- R2 必须列出搜索过的函数、文件位置、当前职责、建议目标模块、history 风险和 smoke 建议。
- R2 不允许修改源码、脚本、文档或运行 git 写操作。
- Codex 只采纳能被本地源码定位和现有 bug 证据支持的结论。
- 若 R2 输出泛化建议但无法对应具体函数,视为不可采纳,只记录为 worker 质量问题。
10.1 Batch 0 执行记录(2026-05-24)
Reasonix R2 run:
- run id:
reasonix-2026-05-24T12-38-22-683Z-3826b3b6 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T12-38-22-683Z-3826b3b6/process-handoff.md - 类型:只读审计,无工作区改动。
- Codex 复核:已抽样用
rg核对关键符号位置;Hindsightreasonixbank 可 recall 到该 handoff。
可采纳结论:
- 最近已修复的附件链接
Delete/Backspace路径现在使用editor.delete_selection(),属于可迁入history_safe_commands.rs的正向模板。 selected_local_attachment_link_text和selected_local_attachment_link_in_editor_state可优先迁入attachment_links.rs,迁移本身应保持行为不变并继续跑task488。- 仍需登记的高风险用户编辑路径:
- 块菜单 delete:
delete_top_level_block_html->apply_html_update->editor.set_content(TiptapContent::html(...)),用户删除动作绕过 history,后续应替换为 Tiptap command/transaction。 - 块类型转换或 slash action 中经过
apply_document_update的路径:editor.set_content(TiptapContent::json(...))可能绕过 history,需要逐调用点确认。 run_block_turn_into_page_action/run_block_turn_into_page:整文 JSON 替换,是否可撤销属于产品决策,不在第一刀默认修改。
- 块菜单 delete:
- 可接受的
set_content路径:apply_host_document_payload- 外部 block delta listener
- 编辑器初始化恢复本地草稿 这些是非用户直接编辑动作,允许保留,但必须避免污染用户 undo stack 和覆盖当前用户编辑。
本轮发现的 worker 协同问题:
- Reasonix 对大文件只读审计有效,但输出中的“行号和风险分级”只能作为候选事实;主控必须用本地
rg/ 源码抽样复核。 - Reasonix runner 产出
completed结果后底层reasonix acp进程未自动退出;主控必须在每批结束时检查并清理本轮启动进程。
10.2 Batch 1 执行记录(2026-05-24)
Reasonix run:
- run id:
reasonix-2026-05-24T14-00-12-979Z-1125fb29 - worker worktree:
/mnt/Data1T/mnote-wt-5-28-attachment-history-b1 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T14-00-12-979Z-1125fb29/process-handoff.md - 类型:窄范围机械迁移,允许修改
src/lib.rs和src/editor_runtime/{mod.rs,attachment_links.rs,history_safe_commands.rs}。
采纳内容:
- 新增
editor_runtime/mod.rs。 - 新增
editor_runtime/attachment_links.rs,迁出本地附件链接的 DOM selection fallback 与 Tiptap editor state link attributes 判断。 - 新增
editor_runtime/history_safe_commands.rs,把选中本地附件链接后的Delete/Backspace删除收口到delete_selected_attachment_link_with_history(...)。 src/lib.rs只保留模块装配和 keydown handler 调用,不再直接承载本地附件链接识别函数。
Codex 主控复核修正:
- worker 初版把底层错误格式化为
删除附件引用失败:{e},调用处也会加同一前缀;主控已改为error.to_string(),避免用户反馈重复。 - worker 初版自报
cargo check通过依赖临时reference-codesymlink;主控在 worktree 和主工作区分别重新验证。 - Reasonix runner 写出 completed 后底层
reasonix acp残留,主控已清理本轮进程。 - 后续
task479暴露 DOMrange.selectNode(link)+Backspace场景下 Tiptap selection 可能未同步,导致相邻附件删除检查失败;主控已在history_safe_commands删除前把 DOM 选中的本地附件链接映射回 Tiptap text selection,再调用delete_selection()。
已通过验证:
cargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml(35 个既有 warning)cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --releasewasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-islandnode scripts/task488-local-attachment-link-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task488-local-attachment-link-delete-undo-smoke/result.json
- 结果:
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true - 证据:命令 stdout JSON,覆盖
editor-upload-to-page-resource-dir、filetree-folder-drop-to-target、broken-link-after-real-file-delete、direct-attach-open-without-active-md、editor-delete-one-attachment-preserves-adjacent等检查。
- 结果:
node scripts/task488-local-attachment-link-delete-undo-smoke.js- 结果:
ok=true - 步骤:
uploaded -> selected -> deleted -> undo -> redo - 证据:
tmp/task488-local-attachment-link-delete-undo-smoke/result.json、tmp/task488-local-attachment-link-delete-undo-smoke/after-redo.png
- 结果:
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true - 覆盖:单附件
Backspace删除保留相邻附件、真实附件文件不被级联删除、刷新后被删引用不回来。
- 结果:
codegraph index . --force && codegraph status .- 结果:索引 318 个文件,状态 up to date。
- 复核:
delete_selected_attachment_link_with_history、selected_local_attachment_link_text、is_local_attachment_link_selected均可通过 CodeGraph 搜索定位到editor_runtime/*。
10.3 Batch 2 执行记录(2026-05-24)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T15-01-33-138Z-dc32246a - worker worktree:
/mnt/Data1T/mnote-wt-5-28-command-sync-b2 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-01-33-138Z-dc32246a/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、process-handoff.json。
采纳内容:
- 新增
editor_runtime/command_sync.rs,迁出read_editor_snapshot、sync_editor_outputs、sync_persisted_editor_command。 - 新增
editor_runtime/persistence.rs,迁出PersistedSpikeDocument、PersistedDocumentIdentity、identity normalization、storage key、localStorage load/persist。 apply_document_update迁入command_sync.rs,保持迁移前零调用者现状。apply_html_update暂留lib.rs:它仍强依赖 host change/status event、HoveredBlockState和 toolbar overlay state;后续等bridge_events.rs/overlays.rs拆出后再迁。
Codex 主控复核:
persist_document_state的 payload、storage key、localStorage写入错误文案保持不变。sync_persisted_editor_command现在委托persist_current_editor_snapshot(...),dirty count、自身快照读取、HTML/JSON output 更新和反馈文案顺序保持不变。normalize_identity_value改为pub(crate),用于 mindmap draft identity;persisted_document_storage_key只在#[cfg(test)]下导入。
已通过验证:
cargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml(35 个既有 warning)cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release(35 个既有 warning)wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-island
受阻验证:
cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml persisted_document_storage_key_isolated_per_document_identitycargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml standalone_mindmap_object_uses_isolated_draft_identity- 阻塞原因:lib test 编译阶段命中既有
slash_menu_css_uses_viewport_overlay_layer中STYLE未定义;HEAD 中该测试已引用STYLE,而实际常量名为SPIKE_STYLE。该问题不属于本批拆分,已记录为后续单独处理。
10.4 Batch 3 第一刀执行记录(2026-05-24)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T15-17-16-430Z-18054487 - worker worktree:
/mnt/Data1T/mnote-wt-5-28-upload-boundary-b3 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-17-16-430Z-18054487/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、process-handoff.json。
采纳结论:
- 当前上传链路应保持
editor intent -> CustomEvent -> shell pickup。 attachment_upload.rs只承接dispatch_editor_upload_request(kind, accept)这一类编辑器 intent,不迁入真实上传、文件选择器、API POST、资源归属或插入链接主流程。local_markdown_paths.rs暂不在leptos-tiptap内创建;本地 Markdown 路径、rootUri、/api/local-folder/files/openURL、markdownRelativePath与落盘语义归03-rust-webshell host runtime /local-upload-runtime.js。
采纳内容:
- 新增
editor_runtime/attachment_upload.rs,迁出dispatch_editor_upload_request(kind, accept)。 src/lib.rs的run_slash_action继续保留SlashActionKind::Image/UploadAttachment的 slash menu 决策,只调用attachment_upload发出上传 intent。src/lib.rs不再直接构造mnote:editor-upload-request。
已通过验证:
cargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml(35 个既有 warning)cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release(35 个既有 warning)wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-islandnode scripts/task488-local-attachment-link-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task488-local-attachment-link-delete-undo-smoke/result.json
- 结果:
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true - 证据:命令 stdout JSON,覆盖
editor-upload-to-page-resource-dir、filetree-folder-drop-to-target、broken-link-after-real-file-delete、direct-attach-open-without-active-md、editor-delete-one-attachment-preserves-adjacent等检查。
- 结果:
后续归属:
resolveEditorUploadContext、editorRootFromUploadOptions、openEditorUploadFilePicker等 shell-side DOM/context 探针适合进入03-rust-web的local-upload-runtime.js后续批次。uploadFileToMediaAsset、uploadFilesWithResolvedTarget、insertUploadedAssetIntoEditor仍是高耦合主流程,暂不迁入 editor runtime。
10.5 Batch 4 块菜单删除 history 收口(2026-05-24)
Codex 主控直接完成:
- 新增
scripts/task489-block-menu-delete-undo-smoke.js,先在旧实现上确认 RED:块菜单删除Beta block后,Ctrl+Z未恢复目标块,失败证据写入tmp/task489-block-menu-delete-undo-smoke/result.json。 editor_runtime/history_safe_commands.rs新增delete_top_level_block_with_history(editor, index):- 多块文档:计算顶层块完整 ProseMirror range 后调用 Tiptap
delete_range(...)。 - 单块文档:用
insert_content_at(range, paragraph, update_selection=true)替换为空段落。 - 删除完成后仅调用
sync_persisted_editor_command(...)同步快照和草稿,不再走apply_html_update。
- 多块文档:计算顶层块完整 ProseMirror range 后调用 Tiptap
- 删除
src/lib.rs中旧的delete_top_level_block_html(...),避免块菜单删除继续保留 HTML 整文替换入口。
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T15-45-44-865Z-bb0ede13 - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T15-45-44-865Z-bb0ede13/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall、
process-handoff.md、process-handoff.json。 - 采纳结论:可采纳;块菜单删除已从
apply_html_update(...)/set_content(...)路径迁到 Tiptapdelete_range(...)/insert_content_at(...)。Reasonix 提醒sync_persisted_editor_command(...)与 Tiptapon_change后续可能存在 dirty count / localStorage 冗余,作为后续优化风险登记,不阻塞本批。
已通过验证:
node --check scripts/task489-block-menu-delete-undo-smoke.jscargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml(35 个既有 warning)cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release(35 个既有 warning)wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-islandnode scripts/task489-block-menu-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task489-block-menu-delete-undo-smoke/result.json、tmp/task489-block-menu-delete-undo-smoke/after-redo.png
- 结果:
node scripts/task488-local-attachment-link-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task488-local-attachment-link-delete-undo-smoke/result.json
- 结果:
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js- 结果:
ok=true - 证据:命令 stdout JSON,覆盖上传目录、文件树 drop、broken link、无 active document 附件 tab、标题来源、附件键盘/手柄删除。
- 结果:
- smoke 启动方式:
- 使用稳定入口
MNOTE_WEB_BIND=0.0.0.0:3000 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:3000 cargo run --manifest-path rust/Cargo.toml -p mnote-web --bin mnote-web。 npm run dev:hot/cargo watch在本轮验收中发生过重编译重启,导致task479中段ERR_CONNECTION_REFUSED;该结果已废弃,不作为功能失败证据。
- 使用稳定入口
codegraph search delete_top_level_block_with_history top_level_block_full_range prosemirror_node_size- 结果:
delete_top_level_block_with_history、top_level_block_full_range、prosemirror_node_size可定位到rust/spikes/leptos-tiptap-spike/src/editor_runtime/history_safe_commands.rs。
- 结果:
剩余风险:
- 块手柄拖拽重排和复制副本仍使用 HTML 整文替换,继续登记为后续 history-safe command 切片;本设计 done gate 只要求至少一个已登记用户编辑路径完成迁移。
10.6 后续 P4 只读审计补充(2026-05-25)
Reasonix read-only audit:
- run id:
reasonix-2026-05-24T16-25-22-430Z-3a9a1a82 - worktree:
/mnt/Data1T/mnote-worktrees/5-28-dom-bridge-audit - handoff:
/home/lix/.codex/runtime/reasonix-coding-worker/reasonix-2026-05-24T16-25-22-430Z-3a9a1a82/process-handoff.md - 类型:只读审计,主控已读取 Hindsight recall 与
process-handoff.md。
可采纳结论:
- 后续 P4 第一刀应优先新增
editor_runtime/dom_selection.rs,而不是直接抽block_menu.rs。 - 第一刀候选函数:
active_editor_text_selectionselection_summaryhas_active_text_selectionselection_has_rich_marksselection_payload/ selection event payload 构造send_selection_state/send_selection_state_to_target
block_menu.rs暂不先做:当前 block menu / overlay 相关函数仍强依赖大量ReadSignal/WriteSignal,在合并 overlay state 或先迁出HoveredBlockState前,直接抽模块只会把长参数耦合搬到新文件。overlays.rs可作为dom_selection.rs之后的第二刀;apply_html_update仍等bridge_events.rs/ overlay state 收口后再迁。
建议验证:
cargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.tomlcargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --releasewasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-islandnode scripts/task488-local-attachment-link-delete-undo-smoke.jsnode scripts/task489-block-menu-delete-undo-smoke.js
10.7 Batch 5 DOM selection 第一刀(2026-05-25)
Codex 主控基于上一批 Reasonix P4 只读审计直接完成:
- 新增
editor_runtime/dom_selection.rs。 - 迁出纯 DOM / selection helper:
node_is_within_rootactive_editor_text_selectionhas_active_text_selectionselection_summaryselection_has_rich_marks
src/lib.rs保留SelectionPayload、selection_payload、send_selection_state、send_selection_state_to_target、selection_event_payload和事件 dispatch;这些仍依赖 bridge event、HoveredBlockState、block id 解析和 target dispatch,后续等bridge_events.rs/ overlay state 边界更稳后再迁。editor_root_element()改为pub(crate),供dom_selection.rs读取当前编辑器根节点;shell/resource 语义未进入 editor DOM 模块。
已通过验证:
cargo fmt --manifest-path rust/Cargo.toml --all --checkcargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml(35 个既有 warning)cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release(35 个既有 warning)wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-islandnode scripts/task488-local-attachment-link-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task488-local-attachment-link-delete-undo-smoke/result.json
- 结果:
node scripts/task489-block-menu-delete-undo-smoke.js- 结果:
ok=true - 证据:
tmp/task489-block-menu-delete-undo-smoke/result.json - 备注:首次与
task488并行执行时在Ctrl+Z等待上超时;单独重跑通过,后续 smoke 建议串行跑编辑器键盘类场景,避免浏览器/服务端时序争用误报。
- 结果:
后续建议:
- 下一刀可迁
floating_toolbar_anchor_from_selection/sync_text_selection_overlay等 overlay anchor 纯计算部分进入overlays.rs。 selection_payload/send_selection_state*应与bridge_events.rs一起迁,避免为了单次拆分扩大SelectionPayload、HoveredBlockState和 runtime block id 的可见性。
11. 测试与验收
Rust 检查
cargo fmt --manifest-path rust/Cargo.toml --all --check
cargo check --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml
cargo build --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml --target wasm32-unknown-unknown --release
wasm-bindgen rust/spikes/leptos-tiptap-spike/target/wasm32-unknown-unknown/release/mnote_leptos_tiptap_spike.wasm --target web --out-dir rust/spikes/leptos-tiptap-spike/generated/island --out-name mnote-leptos-tiptap-spike-island
Browser smoke
node scripts/task488-local-attachment-link-delete-undo-smoke.js
node scripts/task489-block-menu-delete-undo-smoke.js
node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
node scripts/task472-side-target-secondary-pane-smoke.js
后续拆到 block menu / table toolbar 时,应补对应 smoke,不复用附件 smoke 代替。
12. 风险与控制
- 风险:拆模块时改变 Leptos signal 捕获和生命周期。
- 控制:每次只迁一个行为域;迁移后立即跑 wasm build 和浏览器 smoke。
- 风险:为了通过编译把函数参数扩得过大。
- 控制:只在边界稳定后引入小型 context struct;不要先抽象大而全 runtime context。
- 风险:把 shell/resource 语义搬进 editor 模块。
- 控制:editor 只发出 intent,不决定 workspace/resource 真相。
- 风险:继续产生新的巨型模块。
- 控制:单模块超过约 800 行必须拆出子模块。
13. Done Gate
本设计移动到 done/ 前必须满足:
attachment_links.rs和history_safe_commands.rs已落地。command_sync.rs已落地,用户编辑动作与 host 替换动作边界明确。- 至少一个仍使用
set_content的用户编辑路径已迁到 Tiptap command/transaction。 task488通过,且至少覆盖一次真实键盘Ctrl+Z/Ctrl+Y。- 新增 editor_runtime Rust 模块能被 CodeGraph 定位。
src/lib.rs不再新增附件/history/DOM bridge 大块逻辑。
14. 后续问题
- 是否把
leptos-tiptap-spike从 spike 正式改名:不作为本设计前置,等模块拆分后再评估。 - 是否回 upstream
reference-code/leptos-tiptap增加命令:只有当底层 bridge 缺少通用 Tiptap command 时才考虑;MNote 特有行为优先留在editor_runtime。 - 是否引入更强类型的 editor command contract:P1-P3 完成后再评估,不作为第一阶段阻塞项。