chore: land tree view-state, vault, Pi module split, and repo hygiene

Persist PageTree expand state via control-plane view-state and align
chevron/DOM with restored expansion; keep Sidex-style shallow page-tree
scan and drop the unused recursive scanner that only added cargo noise.

Add password vault workbench routes/runtime/skill/CLI, split page_ai_pi
into a module package, and retire Hermes/ACP/OpenHub recycle + root
harness evidence from the index while gitignoring recycle and local
diag dumps.

Archive superseded design/bugs docs under old/, point architecture at
ARCHITECTURE.md, and refresh smokes for Pi S1–S7, vault, and editor
regressions so the working tree can stay clean.
This commit is contained in:
Agent Board
2026-07-21 05:13:05 +08:00
parent 6f9c7d3b58
commit b798f628ee
264 changed files with 17480 additions and 17314 deletions
@@ -1,547 +0,0 @@
# 5-10 [done] Wolai 页面设置与 AI 交互壳对齐方案 v1
> 更新时间:2026-05-06
>
> 状态说明:
> - 本稿对应的页面设置 `popover`、页面 AI `drawer`、入口位置与最小接线方案已在当前 `mnote-web` 文档壳中落地,故迁入 `done/`
> - 本稿中的 deferred 项继续由后续独立任务推进,不影响这一轮“页面设置 + 页面 AI 交互壳”完成判定
> - 2026-05-13 追加说明:本文中 `CLI-first` / `mnote-cli host` 是 2026-05-06 完成时的历史接线口径;当前 AI 长期方向已由 `design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md` 覆盖为 Hermes 页面内客户端 + mnote Hermes skill/plugin
>
> 关联文档:
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-7-wolai-page-tree-main-editor-experience-restoration-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference/5-9-wolai-aline-continuous-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md`
> - `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
## 1. 文档目的
这份文档只回答一个问题:
> **在不制造第二份页面真相、不重开一条 AI 执行面的前提下,如何把 `3000` 文档页的“页面设置”和“AI 界面”收口成更接近 Wolai 的交互壳。**
这次不处理整条文档页体验复刻,也不把评论、协作、演示模式一起打包推进。
本轮优先级固定为:
1. 页面设置入口与面板形态
2. 页面级 AI 入口与右侧抽屉
3. 两者与现有 `Page Aggregate` / Hermes 面板主线的接线方式
一句话收口:
> **先把“入口和容器”做对,再逐项把内部动作接到现有正式命令链。**
---
## 2. 当前判断
### 2.1 当前 `3000` 的问题不是“没有能力”,而是“能力挂错了壳”
当前仓库里已经有可复用能力:
- 页面设置 UI 已存在:`PageOptionsSidebar`
- 页面设置写链已存在:`page.layout.updateOptions`
- 页面 AI host 已存在:`DocumentAiAgentPanel`
- 历史实现已具备 `mnote-cli` host / client 最小返回链;后续应替换为 Hermes 页面内客户端
- 评论 drawer、历史 drawer、分享 dialog 都已有各自最小实现
但当前现网文档页仍然存在两个明显错位:
- 页面设置还是“右侧整块 inspector”思路,不是 Wolai 的右上角 `...` 贴边 popover
- 页面 AI 主要还是顶栏“页面AI”按钮思路,不是 Wolai 的右下角浮动入口 + 右侧抽屉
因此本轮不应重写一套新产品,而应优先做:
> **现有能力重新编排到正确交互壳。**
### 2.2 当前不该做的事
本轮明确不做:
- 不新造第二套页面设置数据结构
- 不新造第二套页面 AI runtime
- 不把 Web AI 面板重新升格成独立 AI 编排主线
- 不为了像 Wolai 而把评论、协作、成员、演示模式一并拉进首批实现
- 不在 Rust SSR 和 React compat 层同时各做一套相同按钮逻辑
---
## 3. Wolai 基线
2026-05-06 已通过 `wolai-aline` 基线取证得到当前 Wolai 行为证据,截图目录:
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/`
关键结论如下。
### 3.1 页面设置
- 入口位于右上角 `...`
- hover tooltip 为“页面选项和全局选项”
- 点击后打开右侧贴边 `popover`,不是 modal,也不是新页面
- URL 保持不变
- 没有遮罩,不压暗背景
- 关闭方式为:
- `Esc`
- 点击页面空白处
- 再次点击同一入口
- 顶部为 `页面选项 / 自定义页面 / 全局选项` 三个 tab
- 可见开关为 checkbox 形态
- 当前主 tab 内有:
- `自适应宽度`
- `小字体`
- `标题目录`
- `标题自动编号`
- `编辑保护`
- 下方还有动作项:
- `删除页面`
- `移动到...`
- `嵌入到...`
- `页面历史...`
- `公开分享页面...`
### 3.2 AI 界面
- 入口位于右下角浮动 `AI` 按钮
- 点击后打开右侧抽屉,不改 URL
- 抽屉没有遮罩,不压暗背景
- 当前关闭方式只有右上角 `X`
- `Esc` 无效
- 点击页面空白处无效
- 入口按钮在抽屉打开后被覆盖,不承担 toggle 关闭语义
- 抽屉标题为“智能问答”
- 中部存在推荐问题/快捷入口
- 底部存在会话区、模型入口、输入框和发送按钮
这说明本轮对齐的最小行为合同已经足够明确:
> **页面设置是“顶栏入口 + 贴边 popover”;页面 AI 是“右下角入口 + 右侧抽屉”。**
---
## 4. 设计原则
### 4.1 页面设置继续服从 `Page Aggregate`
页面设置只能是:
- `pageOptions`
- `editorRuntimePageOptions`
- 对应的 `page.layout.updateOptions`
不能变成:
- 某个单独 UI 组件内部维护的临时配置真相
- 一套只在文档页壳层生效、却不进入主编辑器 runtime 的新设置体系
因此,页面设置改造的重点不是多做几个开关,而是:
> **把现有设置项放进更接近 Wolai 的容器,同时继续显式区分“已正式接通”和“仅保存字段/待接通”。**
### 4.2 页面 AI 继续服从 Hermes 面板主线
页面 AI 界面可以继续做产品壳,但它不能重新变成独立执行面。
当前长期方向已经改为:
- Hermes 是 AI session/message/tool event/usage/model 的会话真相
- Web AI 面板只是页面内 Hermes client
- mnote 通过 Hermes skill/plugin 暴露页面、树、artifact、edge 等业务工具
因此本轮 AI 界面改造只处理:
- 入口位置
- 抽屉壳形态
- 页面级上下文绑定
- 与现有 `DocumentAiAgentPanel` 的挂载方式
本轮不处理:
- AI runtime 重写
- 新模型编排
- 新工具协议
- 第二套全局 AI 路由
### 4.3 先对齐容器语义,再推进动作项
Wolai 里页面设置和 AI 的第一感受,首先是:
- 从哪里打开
- 打开成什么容器
- URL 是否变化
- 如何关闭
这四项比内部是否一开始就全部可用更重要。
因此本轮优先级必须是:
1. 入口位置和按钮密度对齐
2. 容器形态和开关语义对齐
3. 内部动作项逐项复用现有能力
---
## 5. 本轮范围
### 5.1 范围内
- 文档页 owner 态右上角 `...` 页面设置入口
- 页面设置 `popover` 容器
- 页面设置 `tablist` 和最小项分组
- 页面设置最小动作项接线策略
- 文档页右下角浮动 `AI` 入口
- 页面 AI 右侧抽屉容器
- 页面 AI 与现有 `DocumentAiAgentPanel` 的页面级绑定
- 相应 smoke、截图和差异矩阵
### 5.2 暂不进入本轮
- 演示模式
- 页面评论
- 页面协作
- 成员邀请
- 完整公开分享体验重做
- 全局 AI 面板收口
- 帮助中心浮动入口的完整 Wolai 化
- AI 内部推荐内容、模型文案、提示词体系大改
补充:
- `页面历史...``公开分享页面...` 因为当前已有最小能力,可以作为本轮页面设置动作项的优先复用对象
- 评论、协作、成员这类能力虽然在 Wolai 顶栏可见,但当前不进入首批实现
---
## 6. 目标交互合同
## 6.1 页面设置
### 6.1.1 入口
- 入口继续位于文档页右上角操作区
- 视觉上以 `...` 弱按钮承载
- tooltip 调整为“页面选项和全局选项”
- 不再把“页面设置”理解成常驻右侧栏
### 6.1.2 容器
- 使用右侧贴边 `popover`
- 不使用全屏 modal
- 不使用带遮罩的 `Sheet`
- 打开后 URL 不变
- 面板宽度以 Wolai 的紧凑设置面板为准,不沿用当前宽侧栏比例
### 6.1.3 关闭语义
- `Esc` 关闭
- 点击空白关闭
- 再次点击入口关闭
### 6.1.4 内容结构
顶部固定三 tab
- `页面选项`
- `自定义页面`
- `全局选项`
页面选项首批保留并对齐的设置项:
- `wideLayout`
- `smallText`
- `showToc`
- `showHeadingNumbers`
- `protectEditing`
自定义页面首批保留:
- `pageFont`
- `layoutDensity`
- `collapseBacklinks`
- `hideChildPages`
- `showBlockRefCount`
- `embedDefaultBlockId`
全局选项首批只保留当前已有全局偏好中与文档体验直接相关的最小项,不在本轮扩面。
### 6.1.5 动作项
页面设置面板中的动作项分三类:
1. 本轮直接接线
- `页面历史...`
- `公开分享页面...`
2. 本轮保留入口但延后落地
- `移动到...`
- `嵌入到...`
- `删除页面`
3. 本轮不放入页面设置面板
- 评论
- 协作
- 成员邀请
- 演示模式
设计原因:
- `历史``分享` 当前已有组件可复用
- `移动到 / 嵌入到 / 删除` 与树命令、确认流和 picker 交互更深,应拆成后续小任务
- 评论/协作不在本轮优先级内
## 6.2 页面 AI
### 6.2.1 入口
- 文档页主入口改为右下角浮动 `AI` 按钮
- 顶栏“页面AI”不再作为主入口
- 首批可以先去掉顶栏 `页面AI`,或把它降为 debug/临时入口,不再作为默认视觉路径
### 6.2.2 容器
- 使用右侧抽屉
- 不使用遮罩
- 打开后 URL 不变
- 抽屉属于“页面级 AI”,不是全局 AI
### 6.2.3 关闭语义
- 右上角 `X` 关闭
- `Esc` 不关闭
- 点击页面空白不关闭
- 不要求入口按钮承担 toggle 关闭
### 6.2.4 内容结构
抽屉内部继续复用现有 `DocumentAiAgentPanel` 与其 runtime。
本轮只改:
- 外层 chrome
- 标题区与关闭按钮
- 页面级容器布局
- 文档页入口位置和打开方式
本轮不改:
- 历史 `mnote-cli` 执行链
- 工具注册表
- 页面 AI 读写命令族
- AI 输出协议
### 6.2.5 页面级与全局级边界
页面文档内优先展示“页面级 AI”。
全局 AI 继续保留其现有能力,但不在本轮文档页里抢主入口。长期关系应为:
- 文档页浮动 AI:页面上下文优先
- 全局 AI:跨页面/全局工作区能力
---
## 7. 实现落点
## 7.1 顶栏与入口层
当前文档页右上角操作主入口仍由:
- `wolai-frontend/src/components/breadcrumb.tsx`
负责。
因此本轮入口层最终落在当前 `mnote-web` / Rust SSR 文档壳主路径,而不是回到 React compat 交互 owner。
首批调整建议:
- `Breadcrumb` 负责右上角 `...` 入口
- `Breadcrumb` 不再突出“页面AI”主按钮
- 浮动 AI 入口改由文档页内容壳或页面级 host 提供
理由:
- 当前 `3000` 主壳 owner 已是 `mnote-web`
- 页面设置与页面 AI 的入口、容器和状态合同已经在 Rust SSR 文档壳中集中落地
- 继续把入口回接到 React compat 会重新制造双 owner 歧义
## 7.2 页面设置面板组件
当前 `PageOptionsSidebar` 是一个“常驻侧栏”组件,形态上不适合直接复用为 Wolai popover。
建议拆成两层:
1. `PageOptionsPanelContent`
- 只负责 tab、设置项、动作项内容
- 不假设自己是 `aside`
2. `PageOptionsPopover`
- 负责定位、宽度、开关语义、关闭语义
- 作为文档页 owner 态页面设置入口的正式容器
同时保留:
- `PageOptionsSidebar`
作为兼容/开发容器,避免 dev playground 与旧测试全部失效。
### 7.2.1 页面设置状态
当前 `usePageLayoutStore.showInspector` 语义过于偏“右侧常驻 inspector”。
本轮建议把它改造成更接近“页面 chrome 状态”的命名,例如:
- `pageSettingsOpen`
或等价 surface state,但不要把 AI 和页面设置混成一个大 store。
最小原则:
- 页面设置开关状态独立
- 不影响 AI drawer store
- 不重新发明 `pageOptions` 数据真相
## 7.3 页面设置动作项接线
动作项接线优先复用现有能力:
- `页面历史...` -> `DocumentHistoryDrawer`
- `公开分享页面...` -> `DocumentShareDialog`
本轮延后:
- `移动到...`
- `嵌入到...`
- `删除页面`
这些项可以先作为 disabled/coming soon,或只在 checklist 中保留为后续项,但不要假装完成。
## 7.4 页面 AI host
页面 AI 的正式 runtime 继续使用:
- `DocumentAiAgentPanel`
- `DocumentAiAgentPanel.runtime`
本轮只需要把“谁来打开它、以什么容器打开它”改对。
建议:
- 页面级浮动 AI 入口挂在 `DocumentContent` 这一层
- 页面 AI 打开状态继续用 `useAiAgentUiStore.documentAgentOpen`
- 页面 AI 可用态继续由 `DocumentAiAgentPanel` mount 生命周期维护
需要避免:
- 再新增一份 `PageAiDrawer` 独立 runtime
- 把页面 AI 又接回 `GlobalAiAgentHost`
## 7.5 与 Rust SSR 的关系
Rust `mnote-web` 当前在文档壳中已有顶栏占位按钮与右下角浮动按钮。
当前实现已经把 Rust SSR 作为这一轮真实交互 owner,原因是:
- 当前 `3000` 文档页主壳就是 `mnote-web`
- 页面设置与页面 AI 的入口、容器和状态合同已收口到同一份 Rust shell/runtime contract
- React compat 不再承担这一轮页面设置与页面 AI 的默认交互 owner
这意味着本轮完成后的正确做法是:
> **以 Rust SSR / `mnote-web` 文档壳作为默认行为 ownerReact compat 只保留兼容与过渡边界。**
---
## 8. 分阶段实施建议
## 8.1 Phase A:设计与基线冻结
- 固定 Wolai 页面设置和 AI 抽屉的动作链基线
- 新增专属 checklist
- 明确哪些页面设置项本轮保留、隐藏、降级
- 明确页面 AI 与全局 AI 的入口边界
## 8.2 Phase B:页面设置交互壳
-`PageOptionsSidebar` 抽成可复用内容组件
- 文档页顶栏 `...` 接入 `PageOptionsPopover`
- 对齐关闭语义、URL 不变、无遮罩
- 最小接线 `历史``分享`
## 8.3 Phase C:页面 AI 交互壳
- 增加右下角浮动 `AI` 入口
- 让页面 AI 通过右侧抽屉打开
- 去掉顶栏“页面AI”主入口地位
- 对齐 AI drawer 的关闭语义
## 8.4 Phase D:动作项与回归补齐
- 页面设置中逐步接通 `移动到 / 嵌入到 / 删除`
- 评估是否追加帮助浮动入口对齐
- 为 owner/published 两种状态补差异 smoke
---
## 9. 风险与约束
### 9.1 双 owner 风险
当前 `3000` 仍有:
- Rust SSR 产品壳
- React compat 真正交互壳
如果同时在两边各自补交互,容易再次分裂。
本轮约束:
> **入口行为先只认一套真实 owner。**
### 9.2 页面设置“看起来能改,实际没生效”的风险
这仍是页面设置线的核心风险。
本轮约束:
- 未接通项继续显式降级
- 不因为换成 Wolai 容器就把所有项都宣称正式可用
### 9.3 页面 AI 重复造轮子的风险
如果为了像 Wolai 而在 mnote 里新做一层独立 AI drawer runtime,会直接偏离当前 Hermes 面板主线。
本轮约束:
> **页面 AI 交互壳可继续沿用本轮成果,执行主线后续切到 Hermes client proxy + mnote plugin。**
### 9.4 范围失控风险
页面设置一旦带上评论、成员、协作、演示模式,很容易从“交互壳对齐”膨胀成“整页顶栏重做”。
本轮约束:
- 优先做页面设置
- 优先做页面 AI
- 其他项延后
---
## 10. 验收标准
只有同时满足以下条件,才可以说这条线进入实现阶段:
- 页面设置入口、容器、关闭语义已和 Wolai 基线一致
- 页面 AI 入口、容器、关闭语义已和 Wolai 基线一致
- 页面设置仍继续走 `page.layout.updateOptions`
- 页面 AI 交互壳保留右下角入口 + 右侧抽屉;后续执行主线切到 Hermes client proxy + mnote plugin
- 没有新增第二套页面设置真相
- 没有新增第二套页面 AI 执行面
- 评论、协作、成员、演示模式没有被混入首批范围
本轮完成后的正确口径应是:
> **文档页“页面设置”和“AI 界面”的交互壳开始按 Wolai 收口;页面域单一真源保持不变,AI 长期主线改为 Hermes 面板 + mnote plugin。**
@@ -1,365 +0,0 @@
# 5-11 [done] Wolai 页面设置与 AI 交互壳连续执行 checklist v1
> 更新时间:2026-05-06
>
> 状态说明:
> - 本清单覆盖的页面设置 A/B、页面 AI C、集成护栏 D 均已完成并有 `task160/161/162` 证据,故迁入 `done/`
> - `X1-X6` 属于明确 deferred 项,不构成这轮完成阻塞
> - 2026-05-13 追加说明:本文保留 `task161/162` 对旧 `/api/ai-agent/run -> mnote-cli` 返回链的历史验证证据;AI 长期方向已由 `design/07-ai/done/7-3-page-ai-hermes-panel-and-mnote-plugin-v1.md` 改为 Hermes 页面内客户端 + mnote Hermes skill/plugin
>
> 本清单服务于:
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-10-wolai-page-settings-and-ai-surface-alignment-v1.md`
>
> 执行口径继续统一服从:
> - `/home/lix/.codex/skills/wolai-aline/SKILL.md`
> - `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
## 1. 使用方式
每次只领取一个最小行为,按下面闭环推进:
1. 用一句话定义行为,例如“右上角 `...` 打开页面设置 popover 且 URL 不变”。
2. 派 subagent 先取 Wolai 基线。
3. 主线程复核截图并填写差异矩阵。
4. 新增或更新本地 smoke,先让当前实现失败。
5. 小范围实现,不新造第二份页面真相或 AI 执行面。
6. 运行本地 smoke 和相关单测。
7. 再派 subagent 对 Wolai 与本地复测。
8. 主线程复核最终截图。
9. 更新本清单状态、证据路径和剩余差异。
状态标记:
- `TODO`:未开始
- `BASELINE`Wolai 基线已取
- `RED`:本地失败 smoke 已存在
- `GREEN`:本地实现与 smoke 已通过
- `PARITY`:subagent 复测和主线程截图复核通过
- `BLOCKED`:存在阻塞
---
## 2. 当前基线证据
2026-05-06 已完成 Wolai 页面设置与 AI 界面的只读基线取证,目录:
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/`
当前已确认事实:
| 项 | Wolai 证据 | 当前结论 |
| --- | --- | --- |
| 页面设置入口 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/10-page-settings-open.png` | 右上角 `...` 打开右侧贴边 popoverURL 不变 |
| 页面设置关闭 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/11-page-settings-after-esc.png` `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/13-page-settings-after-outside-click.png` `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/15-page-settings-after-reclick.png` | `Esc`、点击空白、再次点击入口都可关闭 |
| 页面设置结构 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/10-page-settings-open.json` | 3 个 tab,内部主控件是 checkbox 形态 |
| AI 入口 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/20-ai-open.png` | 右下角浮动 `AI` 按钮打开右侧抽屉,URL 不变 |
| AI 关闭 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/21-ai-after-esc.png` `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/23-ai-after-outside-click.png` `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/27-ai-after-close-button.png` | `Esc` 和点击空白无效,只能点右上角 `X` |
| AI 结构 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/page-settings-ai-baseline/20-ai-open.json` | 标题“智能问答”,有会话区、模型区、输入框和发送按钮 |
### 2.1 用户追加截图复核(2026-05-06
用户追加的当前 `3000` 截图:
- `/mnt/Data1T/mnote/tmp/image copy 44.png`
- `/mnt/Data1T/mnote/tmp/image copy 45.png`
当前补充判断:
- `image copy 44.png``image copy 45.png` 暴露的是三类真实问题:页面设置 tab-panel 混显、页面设置保存后回退、页面 AI 抽屉没有真实文本返回。
- 这三类问题已经在本轮代码修复中逐项落地:
- `styles.rs`:补 `[hidden]` surface 样式,修复 `页面选项 / 自定义页面 / 全局选项` 混显。
- `documents.rs + bridge-runtime + document.rs + layout.rs`:修复页面设置写链、读链和 SSR 首屏注入,解决“闪一下又复原”。
- `compat.rs + layout.rs`:修复真实 `/api/ai-agent/run` 返回文本,页面 AI 默认 provider 对齐为 `hermes`
---
## 3. 通用证据矩阵
每项必须记录下列字段:
| 字段 | 要求 |
| --- | --- |
| Wolai 截图 | 绝对路径,保存在 `/mnt/Data1T/mnote/tmp/wolai-editor-parity/<task>/` |
| 本地截图 | 同视口、同动作链截图 |
| 入口 | 按钮、菜单、浮动入口、快捷键 |
| 关闭路径 | `Esc`、点击空白、再次点击入口、关闭按钮 |
| URL | 操作前后是否变化 |
| 容器类型 | `popover``drawer``sheet``modal` |
| 控件类型 | `checkbox``button``tab``textarea` 等 |
| 控件状态 | `checked``selected``focused``disabled``active` |
| DOM/ARIA | `role``aria-*``data-testid`、可聚焦性 |
| smoke | 对应 `scripts/task*-smoke.js` 或测试文件 |
| 剩余差异 | 不允许只写“基本一致” |
---
## 4. Phase A:页面设置基线与壳层
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| A1 | BASELINE | Wolai 页面设置基线 | 已有 2026-05-06 Wolai 基线截图和 JSON |
| A2 | GREEN | 本地页面设置入口现状基线 | `task160` 已记录并复测当前文档页右上角 `更多` 打开页面设置容器,URL 不变 |
| A3 | GREEN | 页面设置入口 tooltip | `task160` 已断言入口 `title="页面选项和全局选项"` |
| A4 | GREEN | 页面设置容器类型 | `task160` 已断言本地打开右侧贴边 `popover`,不再是空按钮 active |
| A5 | GREEN | 页面设置 URL 合同 | `task160` 已断言打开和关闭页面设置时 URL 不变化 |
| A6 | GREEN | 页面设置关闭语义 | `task160` 已断言 `Esc`、点击空白、再次点击入口全部有效 |
| A7 | GREEN | 页面设置 3 tab | `task160` 已在真实后端端口上断言 `页面选项 / 自定义页面 / 全局选项` 的 tab-panel 隔离行为 |
| A8 | GREEN | 页面选项主控件形态 | `task160` 已断言首 tab 使用 checkbox 行,而不是旧 inspector 大块按钮 |
| A9 | GREEN | 最小设置项保留清单 | `task160` 已断言 `wideLayout / smallText / showToc / showHeadingNumbers / protectEditing` 出现在首 tab |
| A10 | GREEN | 未接通项显式降级 | `task160` 已断言 `showToc / protectEditing` 当前 disabled 且带“待接线”说明 |
### 4.1 本阶段 smoke 建议
- `scripts/task160-wolai-page-settings-shell-smoke.js`
首轮最小断言建议:
- 点击右上角 `...` 后 URL 不变
- 出现页面设置 `popover`
- 出现三 tab
- `Esc` 可关闭
- 点击页面空白可关闭
- 再次点击入口可关闭
### 4.2 task160 页面设置壳执行记录
2026-05-06 已新增并执行 `scripts/task160-wolai-page-settings-shell-smoke.js`,用于固化 A2-A7 的本地 RED 基线。
RED
- 命令:`node scripts/task160-wolai-page-settings-shell-smoke.js`
- 结果:失败
- 失败信息:`页面设置入口必须打开右侧贴边 popover`
- 当前本地状态:
- 点击右上角 `更多` 后 URL 保持文档页不变
- 顶栏 `更多` 获得焦点,但没有打开页面设置容器
- 当前页面中未出现页面设置 popover;探测到的唯一 `tablist` 仍是左侧 `我的页面 / Explorer / +`
证据路径:
- 本地点击后截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/01-after-click-more.png`
- 本地点击后状态:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/01-after-click-more.json`
- 本地失败截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/failure.png`
- 本地失败状态:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/failure-state.json`
当前判断:
- A2 已有本地基线
- A4 已形成真实 RED
- A5 目前从 failure-state 可见点击后 URL 未变化,但完整打开/关闭合同仍待容器实现后继续验证
- A6-A7 仍待容器出现后继续验证
GREEN
- 命令:`node scripts/task160-wolai-page-settings-shell-smoke.js`
- 结果:通过
- 当前通过行为:
- 右上角 `更多` 打开页面设置 `popover`
- URL 保持不变
- `Esc`、点击空白、再次点击入口均可关闭
- 顶部存在 `页面选项 / 自定义页面 / 全局选项`
- 页面设置首 tab 使用 checkbox 行
- `showToc / protectEditing` 当前显式降级
新增证据:
- `task160` 最终截图:
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/01-after-click-more.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/02-after-esc.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/03-after-outside-click.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task160-page-settings-shell/04-after-reclick.png`
修复补充(2026-05-06):
-`styles.rs` 中补了 `.wolai-page-settings-section[hidden] { display: none !important; }`
- `task160` 随后已在真实后端端口 `41327` 上验证 `页面选项` 不再混出 `自定义页面` 字段,`自定义页面 / 全局选项` 的 panel 行为恢复正常
---
## 5. Phase B:页面设置动作项与现有能力接线
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| B1 | GREEN | `页面历史...` 接线 | `task160` 已断言页面设置动作项可打开 Rust Web 页面历史抽屉 |
| B2 | GREEN | `公开分享页面...` 接线 | `task160` 已断言页面设置动作项可打开 Rust Web 公开分享对话框 |
| B3 | GREEN | `移动到...` 入口策略 | `task160` 已断言当前显式为 disabled/placeholder |
| B4 | GREEN | `嵌入到...` 入口策略 | `task160` 已断言当前显式为 disabled/placeholder |
| B5 | GREEN | `删除页面` 入口策略 | `task160` 已断言当前显式为 disabled/placeholder |
| B6 | GREEN | 统计信息布局 | `task160` 已断言底部存在紧凑统计信息区 |
| B7 | GREEN | 页面设置项真实生效 | `task160` 已在真实后端端口上断言 `wideLayout``layoutDensity` 持久化并在刷新后回读成功 |
### 5.1 本阶段 smoke 建议
-`task160` 基础上扩展动作项断言
- 新增针对已接通选项的可见结果断言
最低要求:
- 历史入口可打开现有历史抽屉
- 分享入口可打开现有分享对话框
- `wideLayout``smallText``layoutDensity` 这类已接通项能在页面可见变化中被捕获
### 5.2 task160 Phase B 执行记录
2026-05-06 `task160` 已继续覆盖 B1-B7,当前通过:
- `页面历史...` 可打开 `data-testid="wolai-page-history-drawer"`
- `公开分享页面...` 可打开 `data-testid="wolai-page-share-dialog"`
- `移动到... / 嵌入到... / 删除页面` 当前显式 disabled
- 页面设置底部显示字数、字符、块数、待办统计
- `wideLayout` 写链真实命中 `/api/documents/options`
- `layoutDensity` 写链真实命中 `/api/documents/options`
- `wideLayout` 打开后 `.document-shell` 宽度真实增大
修复补充(2026-05-06):
- 先修了 `mnote-web` 页面设置 route payload 与 `bridge-runtime` command plan:不再把 `workspaceId` 和一组 `null` 选项继续下发到 `documents:updateOptions`
- 再修了 `DocumentPage` 的 SSR `data-page-*` 首屏注入,以及 `SIDEBAR_TREE_JS` 初始化顺序,避免在 `__MNOTE_PAGE_AGGREGATE__` 还未进入 DOM 时把默认 page options 缓存死
- 之后通过真实后端端口 `41327` 验证:
- `/api/documents/options` 返回 200
- `/api/page-aggregate/:id` 能读回 `wideLayout=true``layoutDensity=compact`
- `task160` 刷新后 checkbox 和壳层 attr 不再回退
---
## 6. Phase C:页面 AI 入口与抽屉壳
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| C1 | BASELINE | Wolai 页面 AI 基线 | 已有 2026-05-06 Wolai 基线截图和 JSON |
| C2 | GREEN | 本地页面 AI 入口现状基线 | `task161` 已记录并复测右下角 AI 可打开页面 AI 抽屉,URL 不变 |
| C3 | GREEN | 页面 AI 主入口迁到右下角 | `task161` 已断言页面级 AI 主入口为右下角浮动按钮 |
| C4 | GREEN | 顶栏 `页面AI` 降级 | `task161` 已断言顶栏不再保留 `页面AI` 主入口 |
| C5 | GREEN | 页面 AI 容器类型 | `task161` 已断言页面 AI 以右侧抽屉打开、无遮罩、不跳新页面 |
| C6 | GREEN | 页面 AI 关闭语义 | `task161` 已断言 `Esc`、点击空白无效,右上角 `X` 有效 |
| C7 | GREEN | 页面 AI 页面级绑定 | `task161` 已断言历史 `/api/ai-agent/run` 请求体携带当前 `documentId``pageOptions`;后续同类上下文应进入 Hermes run/session context |
| C8 | GREEN | 页面 AI 历史返回链不分裂 | 真实 `/api/ai-agent/run` 曾改为 `Next 兼容优先 -> 本地 mnote-cli fallback -> 旧 orchestrator 兜底``task162` 已验证真实页面可返回文本结果;长期主线已改为 Hermes client proxy + mnote plugin |
| C9 | GREEN | AI chrome 与 Wolai 接近 | `task161` 已断言标题、输入区、`新会话 / 历史会话 / mnote-cli` chrome 可见;后续文案应改为 Hermes session / model / history |
### 6.1 本阶段 smoke 建议
- 本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
首轮最小断言建议:
- 点击右下角 AI 后 URL 不变
- 打开右侧抽屉
- 抽屉无遮罩
- `Esc` 不关闭
- 点击页面空白不关闭
- 点右上角关闭按钮可关闭
### 6.2 task161 页面 AI 壳执行记录
2026-05-06 已新增并执行 `scripts/task161-wolai-page-ai-shell-smoke.js`,用于固化 C2-C6 的本地 RED 基线。该脚本现已随旧 `/api/ai-agent/run` smoke 迁入本机 gitignored `recycle/scripts/retired-ai-agent-run-smokes/`,只保留历史对照意义。
RED
- 历史命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`;当前本机归档路径:gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
- 结果:失败
- 失败信息:`页面 AI 入口必须打开右侧抽屉`
- 当前本地状态:
- 点击右下角 `AI 助手` 后 URL 保持文档页不变
- 浮动 AI 按钮没有打开抽屉
- 当前页面中未出现页面 AI 容器、关闭按钮、标题或输入框
证据路径:
- 本地点击后截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/01-after-click-ai.png`
- 本地点击后状态:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/01-after-click-ai.json`
- 本地失败截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/failure.png`
- 本地失败状态:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/failure-state.json`
当前判断:
- C2 已有本地基线
- C5 已形成真实 RED
- C6 仍待抽屉出现后继续验证
- C7-C8 仍待实现阶段继续验证
GREEN
- 历史命令:`node scripts/task161-wolai-page-ai-shell-smoke.js`;当前本机归档路径:gitignored `recycle/scripts/retired-ai-agent-run-smokes/task161-wolai-page-ai-shell-smoke.js`
- 结果:通过
- 当前通过行为:
- 右下角 AI 入口打开页面级右侧抽屉
- URL 保持不变
- `Esc` 和点击空白不会关闭
- 右上角关闭按钮可关闭
- 抽屉标题、输入区、会话/模型 chrome 可见
- 发送动作在历史实现中继续命中 `/api/ai-agent/run`
- 历史请求体继续携带当前 `documentId``pageOptions`;后续应转为 Hermes run/session context
新增证据:
- `task161` 最终截图:
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/01-after-click-ai.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/02-after-esc.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/03-after-outside-click.png`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task161-page-ai-shell/04-after-close-button.png`
修复补充(2026-05-06):
- `compat.rs``/api/ai-agent/run` 已改为:
- 先尝试代理到 Next 的 `/api/ai-agent/run`
- 失败时本地直接 `mnote-cli` host fallback
- 再失败才走旧 orchestrator
- 同时补了认证头显式转发,避免被 Next 侧 307 `/auth` 拦截
- 页面 AI drawer 默认 provider 改为 `hermes`
- 新增 `task162-wolai-page-ai-real-response-smoke.js`,并在真实后端端口 `41327` 上验证:页面 AI 不再显示 `page_ai_failed_502` / “没有返回文本结果”,而是会回填真实文本
- 2026-05-13 口径更新:上述修复只证明历史页面 AI 抽屉可返回文本,不再作为后续 AI 长期执行面依据;新的页面 AI 应直接调用 Hermes client proxy,不再通过 `/api/ai-agent/run` 的 provider / fallback 分支模拟 Hermes
### 6.3 task162 页面 AI 真实返回执行记录
2026-05-06 已新增并执行 `scripts/task162-wolai-page-ai-real-response-smoke.js`,用于补齐 C8 的真实返回验证。
- 命令:`MNOTE_UI_BASE_URL=http://127.0.0.1:41327 node scripts/task162-wolai-page-ai-real-response-smoke.js`
- 结果:通过
- 当前通过行为:
- 页面 AI 不再出现 `page_ai_failed_502`
- 不再显示“当前页面 AI 已接通 \`mnote-cli\`,但这次没有返回文本结果。”
- 页面 AI 会回填真实 `mnote-cli` 输出文本
---
## 7. Phase D:页面 AI 与页面设置的集成护栏
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| D1 | GREEN | 不新造页面设置真相 | 页面设置继续围绕 `currentPageOptions -> /api/documents/options -> page.layout.updateOptions` |
| D2 | GREEN | 不新造 AI 执行面 | 历史实现围绕 `/api/ai-agent/run -> mnote-cli` 保持单链;后续不在 mnote 内新造 AI 执行面,而是调用 Hermes 并通过 mnote plugin 写回业务事实 |
| D3 | GREEN | 文档页 owner 行为合同固定 | `task160/task161` 已断言 owner 态页面设置和 AI 都不跳新页面、不改 URL |
| D4 | GREEN | mobile / 窄屏退化 | `task160/task161` 已断言移动视口下 popover / drawer 不超出视口 |
| D5 | GREEN | React compat 与 Rust SSR 边界 | 本轮实现仅落在 `mnote-web` Rust SSR 壳、脚本与样式;未再把入口回接 React compat |
| D6 | GREEN | checklist 与失败模式回填 | 已向 `wolai-aline/references/failure-patterns.md` 新增“本地 3000 进程未重启,smoke 误打旧实现” |
---
## 8. Deferred 清单
以下项当前明确延后,不混入首批验收:
| ID | 状态 | 任务 | 说明 |
| --- | --- | --- | --- |
| X1 | TODO | 演示模式 | 不是本轮页面设置与 AI 壳的主线 |
| X2 | TODO | 页面评论 | 用户已明确可延后 |
| X3 | TODO | 页面协作 | 用户已明确可延后 |
| X4 | TODO | 成员邀请 | 顶栏 owner 态能力,后续单拆 |
| X5 | TODO | 全局 AI 入口重构 | 本轮只做页面级 AI |
| X6 | TODO | 帮助浮动入口完全对齐 | 本轮优先 AI,不把帮助入口一起扩面 |
---
## 9. 完成判定
本清单只有在以下条件同时成立时,才可以视为本轮设计进入可实现状态:
- 页面设置 `popover` 行为已通过本地 smoke 和 Wolai 基线复核
- 页面 AI 抽屉行为已通过本地 smoke 和 Wolai 基线复核
- 页面设置仍然围绕 `page.layout.updateOptions`
- 页面 AI 历史验证仍然围绕 `/api/ai-agent/run -> mnote-cli`;后续执行主线改为 Hermes client proxy + mnote plugin
- deferred 项没有被误报为已完成
本轮完成后的正确口径:
> **页面设置与页面 AI 的交互壳已开始对齐 Wolai;AI 后续执行主线改为 Hermes 面板 + mnote plugin,评论、协作、成员、演示模式仍是后续独立任务。**
@@ -1,402 +0,0 @@
# 5-13 [done] 页面块身份与命令合同 v1
> 更新时间:2026-05-16
>
> 当前状态:`DONE`。
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/done/2-1-page-block-storage-projection-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/07-ai/done/7-9-page-block-ai-tooling-roadmap-v1.md`
> - `/mnt/Data1T/mnote/design/old/07-ai/process/7-10-page-block-ai-tooling-execution-checklist-v1.md`
>
> 代码依据:
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/model.rs`
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/command.rs`
> - `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/tiptap.rs`
> - `/mnt/Data1T/mnote/rust/crates/bridge-runtime/src/lib.rs`
>
> 外部参考:
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/agents/tools/index.mdx`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/api-reference/read-the-document.mdx`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-docs/src/content/content-ai/capabilities/ai-toolkit/api-reference/execute-tool.mdx`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-main/packages/extension-unique-id/src/unique-id.ts`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-main/packages/server-ai-toolkit/src/hash-extension/server-ai-toolkit-hash-extension.ts`
---
## 1. 结论
页面块 AI 工具不能建立在“整页 `content` 字符串/数组 patch”之上。它必须先有一个稳定的页面块合同:
> **Rust `EditorBlockDocument` 是页面正文的 canonical block view`EditorBlock.block_id` 是 mnote 页面内块身份真相;Tiptap JSON、ProseMirror position、`UniqueID`、AI Toolkit `_hash` 都只能是 runtime adapter 或定位辅助。**
第一阶段允许底层仍回写整份 `documents.content`,但命令语义必须先落到 Rust block command
- `editor.block.replace`
- `editor.block.insert_after`
- `editor.block.delete`
- `editor.block.move_after`
也就是说,持久化可以暂时是 snapshot save,工具合同不能退化成 snapshot patch。
---
## 2. 当前真实状态
### 2.1 已经存在的基础
`core-protocol` 已经有正式块模型:
- `EditorBlockDocument.document_id`
- `EditorBlockDocument.root_block_ids`
- `EditorBlockDocument.blocks`
- `EditorBlock.block_id`
- `EditorBlock.block_type`
- `EditorBlock.props`
- `EditorBlock.content_nodes`
- `EditorBlock.child_block_ids`
`core-protocol` 也已经有编辑命令形态:
- `EditorReplaceBlock`
- `EditorInsertBlockAfter`
- `EditorDeleteBlock`
- `EditorMoveBlock`
- `EditorSplitBlock`
- `EditorMergeWithPrevious`
- `EditorIndentBlock`
- `EditorOutdentBlock`
`core-protocol/src/editor/tiptap.rs` 已经提供 `EditorBlockDocumentTiptapBridge`,能在 `EditorBlockDocument` 与 Tiptap JSON 之间转换,并保留段落、标题、列表、任务、引用、代码、分隔线、图片、表格、目录、mindmap 等类型的阶段性映射。
### 2.2 仍然缺失的合同
现有模型还不足以直接开放成熟 AI 块工具,缺口是:
- `PageAggregate.page_body` 只定义了 `content/revision/conflictDetectionKey`,没有定义 canonical block projection。
- `EditorBlockDocument` 还没有在 Page Aggregate 输出中成为稳定字段。
- `EditorBlock` 没有显式 `parentBlockId/order/path/revisionRef/editable` 投影。
- `doc_insert_blocks/doc_replace_range` 仍是工具雏形,能够生成部分 editor commands,但仍主要围绕 legacy block array 回写。
- `EditorMoveBlock` 有协议形态,但 `mnote.block.move_after` 的父子、排序、复杂块规则尚未冻结。
因此必须先补本合同,再实现 AI 块读写移动。
---
## 3. 块身份规则
### 3.1 正式身份
正式块身份字段固定为:
```json
{
"blockId": "block_abc",
"documentId": "doc_1",
"workspaceId": "ws_1"
}
```
规则:
- `blockId` 在同一 `documentId` 内唯一。
- AI、前端、Tiptap runtime 都不得自行把临时 DOM id 当作 `blockId`
- 新块 id 必须由 Rust runtime 分配,或者由 Rust runtime 校验后接受。
- 保存后再次读取,原块 `blockId` 必须保持不变。
### 3.2 复制规则
复制块时:
- 原块 `blockId` 不复用。
- 新块获得新 `blockId`
- 若复制子树,子块全部获得新 `blockId`
- 块引用、页面引用、mindmap/resource 等外部关联需要单独定义重定向或保留规则,未定义前不得向 AI 开放复制子树工具。
### 3.3 移动规则
移动块时:
- 被移动块 `blockId` 保持不变。
- 同父级移动只改变 sibling order。
- 跨父级移动同时改变 `parentBlockId` 与 order。
- 第一阶段只开放同父级叶子块移动。
- 标题带子块、列表项、表格、mindmap、resource、page reference、block reference 必须在单独规则中定义后再开放。
### 3.4 Tiptap `UniqueID` 与 `_hash`
Tiptap 参考结论:
- `UniqueID` 可以给 Tiptap 节点补 id,并处理协作、粘贴、拖拽场景下的去重。
- Server AI Toolkit 的 `_hash` 是 AI 编辑辅助属性,可帮助定位变化。
- 这两者都不能替代 mnote 的 `EditorBlock.block_id`
mnote 规则:
- Tiptap node attrs 中可以携带 `block_id` / `blockId`,但来源必须是 Rust canonical block projection。
- `_hash` 可以进入 `revisionRef` 的辅助计算,但不能成为业务主键。
- 如果 Tiptap 文档缺少 block id,导入链必须进入“补 id + 记录 warning”的兼容路径,而不是让 AI 直接编辑无身份块。
---
## 4. Canonical Block Projection
`PageAggregate.page_body` 需要新增稳定块投影,建议字段为:
```json
{
"content": [],
"revision": 12,
"conflictDetectionKey": "doc_1:12",
"blockDocument": {
"documentId": "doc_1",
"rootBlockIds": ["b1", "b2"],
"blocks": [
{
"blockId": "b1",
"type": "paragraph",
"text": "正文",
"attrs": {},
"contentNodes": [],
"children": [],
"parentBlockId": null,
"order": "00010000",
"path": [0],
"depth": 0,
"revisionRef": "pageRev:12:block:b1:hash:abc",
"editable": true,
"unsupportedReason": null
}
]
}
}
```
字段说明:
- `blockDocument` 是 Page Aggregate 给 editor/AI/read view 的 canonical block view。
- `content` 保留当前兼容 snapshot,不作为 AI 精确编辑的唯一输入。
- `text` 是 AI 读取用摘要,不替代 `contentNodes`
- `attrs` 是稳定属性投影,不暴露 Tiptap 私有字段;确需保留 Tiptap snapshot 时放入内部 adapter,不进入 AI 默认读取。
- `order` 是同父级排序投影,第一阶段可由数组位置计算,后续可升级为持久 order key。
- `path` 是读取定位辅助,不作为写入主键。
- `revisionRef` 是块级冲突辅助,必须包含 page revision 和块内容/结构摘要。
---
## 5. EditorBlockDocument 与 Tiptap JSON 边界
### 5.1 正式边界
正式边界固定为:
```text
AI / Hermes tool
-> mnote.doc.* / mnote.block.*
-> Rust canonical tool adapter
-> EditorBlockDocument + EditorCommand
-> Tiptap bridge / legacy content bridge / Convex save adapter
```
不得变成:
```text
AI
-> Tiptap JSON 私有 shape
-> Convex documents.content
```
### 5.2 Tiptap JSON 的职责
Tiptap JSON 可以用于:
- 浏览器 editor runtime。
- ProseMirror command 执行。
- 兼容导入/导出。
- 测试 bridge round trip。
Tiptap JSON 不可以用于:
- AI 长期工具入参合同。
- mnote 块身份事实源。
- Convex 长期块结构事实源。
### 5.3 PageMarkdown / PageXML 的职责
AI 对外格式固定为:
- `PageMarkdown`:适合文本、标题、列表、简单块。
- `PageXML`:适合携带 `block-id`、块属性、结构化块。
- `json`:只能是 mnote canonical block projection,不是裸 Tiptap JSON。
---
## 6. 块命令合同
### 6.1 `editor.block.replace`
语义:
> 替换目标块的类型、属性或内容,不改变目标块 `blockId`、父级、同级顺序。
输入:
```json
{
"documentId": "doc_1",
"blockId": "b1",
"blockType": "paragraph",
"props": {},
"contentNodes": [
{ "type": "text", "text": "替换后的正文", "marks": [] }
],
"expectedRevision": 12,
"blockRevisionRef": "pageRev:12:block:b1:hash:abc"
}
```
阻断:
- `blockId` 不存在。
- `expectedRevision` 不匹配。
- `blockRevisionRef` 明显不匹配。
- 目标块 `editable=false`
- 目标块是未定义 replace 规则的复杂块。
### 6.2 `editor.block.insert_after`
语义:
> 在 anchor 块同父级后插入一个或多个新块。
输入:
```json
{
"documentId": "doc_1",
"anchorBlockId": "b1",
"blocks": [
{
"type": "todo",
"props": { "checked": false },
"contentNodes": [
{ "type": "text", "text": "新增待办", "marks": [] }
]
}
],
"expectedRevision": 12,
"anchorRevisionRef": "pageRev:12:block:b1:hash:abc"
}
```
规则:
- 新块 id 由 runtime 生成。
- 新块默认进入 anchor 同父级。
- 插入多个块时保持输入顺序。
- 第一阶段不允许把复杂子树作为单次 insert payload。
### 6.3 `editor.block.delete`
语义:
> 删除目标块;是否保留、提升或级联删除子块由命令参数明确指定。
第一阶段不直接开放给 AI 正式写入,只允许 dry-run。
必须先冻结:
- `preserveChildren=true` 时子块提升到哪里。
- `preserveChildren=false` 时删除范围如何展示和审计。
- block reference / comment thread / resource attachment 如何处理。
### 6.4 `editor.block.move_after`
语义:
> 将目标块移动到 anchor 块之后。
第一阶段输入:
```json
{
"documentId": "doc_1",
"blockId": "b3",
"anchorBlockId": "b1",
"expectedRevision": 12,
"blockRevisionRef": "pageRev:12:block:b3:hash:aaa",
"anchorRevisionRef": "pageRev:12:block:b1:hash:bbb"
}
```
第一阶段只允许:
- `blockId``anchorBlockId` 同父级。
- 目标块是叶子块。
- 类型为 `paragraph`、普通 `heading`、普通 `todo`
第一阶段阻断:
- 跨父级移动。
- 移动到自己的子树内。
- 标题带子块整体移动。
- 列表项层级移动。
- 表格、mindmap、resource、page reference、block reference。
- 跨页面移动。
---
## 7. 读工具对命令合同的要求
写工具开放前,读工具必须先能返回:
- `blockId`
- `type`
- `text`
- `attrs`
- `children`
- `parentBlockId`
- `order`
- `path`
- `depth`
- `revisionRef`
- `editable`
- `unsupportedReason`
`mnote.block.fetch` 必须能返回同父级前后文,供 AI 在 `replace/insert_after/move_after` 前做二次确认。
---
## 8. 与 Tiptap AI Toolkit 的关系
Tiptap AI Toolkit 对 mnote 的启发是:
- `tiptapRead` 证明 AI 读工具需要高效、可定位的文档读格式。
- `tiptapEdit` 证明 AI 写工具应该是 operations,而不是整页替换。
- `tiptapReadSelection` 证明 selection/range 需要冻结,不能依赖用户思考期间不断变化的浏览器选择。
- `executeTool/streamTool` 证明工具执行面需要返回 `docChanged`、错误、review/preview 结果。
- schema awareness 证明 AI 需要明确 editor context,而不是猜块类型。
mnote 不直接采用 Tiptap operations 作为外部合同,原因是:
- 公开文档没有完整 operations schema。
- Tiptap operations 面向 ProseMirror 文档层,不覆盖 mnote 的 Rust kernel、Page Aggregate、Convex revision、Hermes audit。
- mnote 需要稳定到跨编辑器、跨存储演进的块合同。
因此 Tiptap 作为参考模型,不作为 mnote 外部工具合同。
---
## 9. 完成定义
本文已归档为 `DONE`,done 边界是“页面块身份、命令和 Tiptap boundary 合同已经冻结为当前代码可执行合同”;后续完整执行矩阵继续由 `7-10` 承接。
- [x] Page Aggregate 输出 canonical `blockDocument` 或等价稳定 block projection。
- [x] `EditorBlockDocument` 与 Tiptap JSON bridge 已覆盖当前主用块类型;更复杂类型继续在 `7-10` 验收矩阵补齐。
- [x] `editor.block.replace` 能生成 canonical content 并回写当前 `documents.content`
- [x] `editor.block.insert_after` 能生成新 block id、正确插入并回读。
- [x] `editor.block.move_after` 已通过同父级叶子块 dry-run / 受限移动链路。
- [x] AI 工具不再把裸 Tiptap JSON 或 Convex `documents.content` 私有结构当长期合同。