# 5-39 Block menu 删除后 Ctrl+Z 不恢复目标块

> 创建时间：2026-05-25
> 状态：`done`
> Owner：`05-editor-mainline`

## 现象

`node scripts/task489-block-menu-delete-undo-smoke.js` 连续两次失败。块菜单删除 `Beta block` 成功，但随后 `Control+Z` 未恢复目标块，最终等待 `Beta block` 重新出现超时。

失败证据：

- `tmp/task489-block-menu-delete-undo-smoke/result.json`
- `tmp/task489-block-menu-delete-undo-smoke/failure.png`

当前 result 摘要：

```json
{
  "ok": false,
  "states": [
    { "step": "opened", "text": "Block Undo\n\nAlpha block\n\nBeta block\n\nGamma block" },
    { "step": "deleted", "text": "Block Undo\n\nAlpha block\n\nGamma block" }
  ],
  "error": "page.waitForFunction: Timeout 30000ms exceeded."
}
```

## 初步判断

失败点不是删除动作本身，而是删除后的 undo 历史不可恢复。当前代码已经调用 `editor_runtime::history_safe_commands::delete_top_level_block_with_history(&editor, block_index)`，需要继续核实：

- `delete_range(range)` 是否真的进入 Tiptap history。
- 删除后 `sync_persisted_editor_command(...)` 是否触发了会清空 undo history 或重建 editor state 的路径。
- 失败是否与焦点丢失有关；需要用最小浏览器复现确认点击 editor 后再 `Control+Z` 是否能恢复。

## 根因

块菜单按钮点击后，删除命令先通过 Tiptap history-safe `delete_range` 路径删除目标块，history 本身仍可用；浏览器诊断确认删除后点击 editor 再 `Control+Z` 可以恢复 `Beta block`。真正断点是删除菜单关闭期间焦点短暂落到 `BODY`，原 smoke 在焦点恢复前立刻发送 `Control+Z`，快捷键没有稳定送到 Tiptap editor，表现为删除成功但撤销不恢复。

## 修复

- 删除菜单项 `mousedown` 阶段阻止默认焦点转移，减少按钮卸载后焦点落回 `BODY` 的窗口。
- 在块菜单删除成功、关闭菜单状态后，异步补一次 `editor.focus()`，确保正常用户操作后的键盘撤销进入 ProseMirror。
- 在 `BODY` 短暂持有焦点时，只兜底转发 `Ctrl/Cmd+Z`、`Ctrl/Cmd+Y`、`Shift+Ctrl/Cmd+Z` 到当前 editor，覆盖 smoke 中删除后立即按撤销的竞态。
- 同批拆出 `editor_runtime/block_menu_document.rs`，隔离 legacy 顶层块 JSON helper；当前运行路径仍优先使用 history-safe command，不回退到 HTML 整文替换删除。

## 验收

- `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` 更新 island wasm。
- `node scripts/task489-block-menu-delete-undo-smoke.js` 通过。
- `node scripts/task488-local-attachment-link-delete-undo-smoke.js` 通过。
- 不得回退到 HTML 整文替换删除路径。

## 未覆盖

- `cargo test --manifest-path rust/spikes/leptos-tiptap-spike/Cargo.toml block_menu_document -- --test-threads=1` 当前被既有 `STYLE` 测试编译错误阻断，错误为 `cannot find value STYLE in this scope`，未进入本次新增 helper 测试。
