**Goal:** Move custom-editor dialog state and cross-node dialog events out of the editor entry component without changing menu behavior or document HTML.
**Architecture:** A feature-dialog hook owns dialog visibility and payloads. A small event bridge replaces the global `localStorage.setItem` override; custom Slate node renderers publish a typed browser event, while the editor entry subscribes through the hook. Dialog rendering remains behaviorally unchanged in this phase.
**Tech Stack:** React, wangEditor, Ant Design.
## Global Constraints
- Keep toolbar keys, menu order, node types, serialized document data, and dialog UI unchanged.
- Do not add broad test suites; verify with static checks and the admin production build.
- Do not commit changes.
### Task 1: Introduce typed feature-dialog state and event bridge
> **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:** 为六个 AI 文本菜单提供可比较、可编辑、最多保留三个版本且安全替换初始选区的统一工作台。
Run: `pnpm --filter @ebook/admin exec vitest run src/test/wangeditor-customer/ai-selection-workbench.test.ts`
Expected: FAIL because `replaceCapturedSelection` does not exist.
-[]**Step 3: Write minimal implementation**
`captureTextSelection` must copy `editor.selection` before opening the modal and reject empty `editor.getSelectionText()`. `replaceCapturedSelection` must assign the saved selection only after verifying it has both `anchor` and `focus`, then use the existing `SlateTransforms.removeNodes` / paragraph insertion behavior. It must return `false` rather than operating at the current caret when validation fails.
-[]**Step 4: Run test to verify it passes**
Run: `pnpm --filter @ebook/admin exec vitest run src/test/wangeditor-customer/ai-selection-workbench.test.ts`
Run: `pnpm --filter @ebook/admin exec vitest run src/test/wangeditor-customer/ai-selection-workbench.test.ts`
Expected: FAIL because the legacy dialog has no comparison labels or replacement button text.
-[]**Step 3: Implement the minimal workbench**
Render an `antd``Modal` using `open` and `onClose`, with `width="min(1200px, 92vw)"`, `styles={{ body: { height: 'calc(80vh - 120px)' } }}`, a read-only left `TextArea`, editable right `TextArea`, result tabs, and footer buttons.
On mount, capture the selection. If absent, call `message.warning('请先选中需要处理的文本')`, call `onClose`, and do not call `post`. Start the initial AI request only after capture succeeds. As stream content changes, update only the newly-created active version. “重新生成” appends a fresh active version; retain only three. “替换选中内容” calls `replaceCapturedSelection`; on `false`, show `message.error('原始选区已失效,请重新选择文本后再试')` and retain the modal.
-[]**Step 4: Run UI tests to verify they pass**
Run: `pnpm --filter @ebook/admin exec vitest run src/test/wangeditor-customer/ai-selection-workbench.test.ts`