对齐 Wolai 侧栏体验并收拢设计入库

This commit is contained in:
lix-2026
2026-04-30 16:18:54 +08:00
parent 8c895b3dc0
commit afb2a5b8a0
89 changed files with 23188 additions and 84 deletions
@@ -0,0 +1,236 @@
# 5-2 [process] Tiptap Notion-Like 模板裁剪与 `leptos-tiptap` 映射分析 v1
> 更新时间:2026-04-19
>
> 这份文档已经按当前进展修正:
>
> 现在的问题不再是“这套模板能不能迁进来”,而是:
>
> **我们已经做出了一版 `leptos-tiptap` 编辑器,接下来应该从官方模板中保留什么行为模型,并优先把它接入主编辑器。**
## 1. 修正后的总判断
当前对官方模板的使用方式应当明确为:
1. 它是体验 benchmark,不是直接照搬对象。
2. 它是行为模型参考,不是 React 代码迁移目标。
3. 现阶段最值钱的不是再扩更多模板功能,而是把已实现能力接入主编辑器。
因此,这份模板的正确用途是:
> **借它定义“官方行为应当是什么”,再用 `leptos-tiptap` + Rust 把这些行为落到 `mnote` 的主链。**
## 2. 官方模板里当前最该保留的参考
### 2.1 主编辑器扩展组合
核心参考文件:
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-templates/notion-like/components/notion-like-editor.tsx`
当前值得保留的不是整份文件,而是这些决策:
- 以 Tiptap 正式 schema 组织常用块
- 常用块优先于低频复杂块
- `task list / task item` 使用正式扩展,不做 HTML 占位
- `UniqueID` 作为块身份辅助机制
- `Indent` 作为普通块缩进增强的参考扩展
### 2.2 slash 菜单行为模型
核心参考文件:
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-ui/slash-dropdown-menu/slash-dropdown-menu.tsx`
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-ui/slash-dropdown-menu/use-slash-dropdown-menu.ts`
当前最该保留的是:
- slash 入口就是块编辑器的主入口之一
- 菜单项的分组与命令组织方式
- 菜单锚点应跟随 caret,而不是随便找一个固定位置
### 2.3 浮动工具条行为模型
核心参考文件:
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-templates/notion-like/components/notion-like-editor-toolbar-floating.tsx`
当前最该保留的是:
- 工具条只在文本 selection 语境下出现
- 文本格式命令和 `turn into` 要分清语境
- 不要把固定显示工具条误当成“功能已完成”
### 2.4 左侧手柄与块菜单行为模型
核心参考文件:
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-ui/drag-context-menu/drag-context-menu.tsx`
当前最该保留的是:
- hover 先只显露手柄,不自动弹块菜单
- 块菜单应由左侧手柄点击触发
- `turn into / duplicate / delete / drag` 是块菜单的核心动作
这点非常关键,因为它直接影响我们后续对标 Wolai/Notion 的交互正确性。
## 3. 结合当前代码后的现实判断
### 3.1 已经有的东西
从当前实现看,下面这些已经不是“要不要做”,而是“如何迁进主链”:
- `paragraph / heading / list / todo / quote / code block / divider`
- slash 菜单
- 浮动工具条
- 左侧手柄
- 块菜单
- `turn into`
- 顶层拖拽
- refresh 后结构保留
对应当前实现入口:
- `/mnt/Data1T/mnote/rust/spikes/leptos-tiptap-spike/src/main.rs`
### 3.2 当前真正的缺口
当前真正的缺口不是模板 feature 数量,而是下面这些:
- 它还在 spike 里,不在主编辑器里
- 保存还没接上正式 Rust truth
- `block_id` 还没成为正式合同
- 页面引用 / 块引用还没打通
- 图片上传与附件桥还没接
- 表格还没做
所以现阶段不该继续把注意力平均分配到:
- Markdown 导入导出回归
- `kode` 组件分层整理
- 表格
- AI Cloud
- 协作链
## 4. `P0.5` 该如何采用官方模板
### 4.1 `P0.5` 要保留的部分
`P0.5` 应保留的不是一堆零散 feature,而是以下几类“官方行为”:
- 主编辑器使用正式 Tiptap 扩展,而不是临时 HTML 拼接
- slash 菜单锚定 caret
- 选中文字才出现浮动工具条
- hover 只显手柄,点击手柄才弹块菜单
- `turn into` 作为块菜单和工具条共享的一组块类型切换动作
- `UniqueID` 作为 runtime 辅助,但最终块 id 仍归 Rust
### 4.2 `P0.5` 不要再继续扩的部分
`P0.5` 不应该继续向下扩这些模板能力:
- 协作
- AI
- TOC
- 完整表格
- 图片上传整链
- 移动端全量工具条
原因很简单:
这些都不会帮助我们更快完成“接入主编辑器”。
## 5. 官方模板到 `mnote` 的映射口径
### 5.1 直接借行为,不借代码
以下部分应当“借行为模型”,不应当尝试直接照搬 React 代码:
- `slash-dropdown-menu`
- `drag-context-menu`
- `notion-like-editor-toolbar-floating`
原因:
- React hooks / context / portal 不能直接进入 Leptos
- 真正有价值的是交互时机、状态边界、动作分组
- 不是 `tsx` 组件本身
### 5.2 `block id` 需要现在就纳入主线
官方模板里的 `UniqueID.configure(...)` 很重要,但口径要修正成:
- 官方参考:
`/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-templates/notion-like/components/notion-like-editor.tsx`
- Rust 真相字段:
`/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/model.rs`
最终原则:
1. Rust `EditorBlock.block_id` 是持久化真相。
2. Tiptap `UniqueID` 只负责浏览器 runtime 内的节点身份辅助。
3. 不允许把前端临时 id 倒灌成正式文档真相。
### 5.3 `Indent` 值得做,但不在这一轮前排
官方缩进扩展很值得参考:
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/materialized/tiptap-extension/indent-extension.ts`
但当前它不应排在主编辑器接入之前。
更合理的顺序是:
1. 先完成主编辑器接入
2. 再完成 Rust 保存边界
3. 再考虑普通块缩进增强
### 5.4 图片上传与表格都先后置
官方模板确实有图片与表格能力,但现在不应让它们进入主 checklist 前排。
原因:
- 图片上传不是当前切流主编辑器的阻塞项
- 表格更不是当前高频刚需
- 两者都容易把工作重新带到复杂 UI / 服务桥接细节
## 6. 哪些旧 checklist 应从主线降级
下面这些项不删,但不应继续放在当前主线前排:
- Markdown 导入导出回归
参考 `blocks` 仍然有价值,但它更像切流后的回归与兼容工作。
-`kode` 做 Leptos 组件分层
这个参考可以保留,但它属于实现注记,不是当前交付里程碑。
- 完整表格
继续后置。
- 完整图片上传
继续后置,只保留后续最小桥接预留。
## 7. 对接下来工作的直接建议
基于当前代码和官方模板,接下来最合理的顺序是:
1. 先把当前 `leptos-tiptap` 编辑器接入主编辑器。
2. 同时建立正式的 Rust load/save 边界。
3. 在这个过程中引入稳定 block id 策略。
4. 等它真正成为主编辑器后,再规划 `P1 / P1.5` 的 Wolai 对标体验增强。
这也意味着:
当前官方模板对我们的最大价值,不是再提供更多可抄的 feature,而是帮助我们定义:
- 哪些交互已经够了
- 哪些交互时机必须修正
- 哪些功能现在根本不该做
## 8. 最终结论
当前对这份模板的正确使用方式是:
> **以后续主编辑器开发继续以 Tiptap 官方能力模型为体验上限,以 `leptos-tiptap` 为运行时接入层;先完成主编辑器接入和 Rust truth 落地,再单独规划 Wolai 对标增强,不再把模板 feature 数量当作当前阶段目标。**
@@ -0,0 +1,591 @@
# 5-5 [process] 主编辑区与树域单一真源对齐方案 v1
> 更新时间:2026-04-22
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
> - `/mnt/Data1T/mnote/design/old/05-editor-mainline/process/5-3-tiptap-leptos-rust-migration-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/process/4-sidebar-pagetree-filetree-rust-web-rebuild-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
## 1. 文档目的
这份文档只回答一个当前已经暴露出来的主线问题:
> **当 `leptos-tiptap` 已经成为页面内正式主编辑区后,页面树 / 文件树 / 页面标题 / 页面设置 / 页面正文,是否已经统一成 Rust 主导的单一真源架构。**
当前结论是:
> **还没有。**
当前系统已经不再是:
- `iframe runtime`
- 纯前端假接入
-`BlockNote` 主链
但也还不是:
- Rust 持有页面聚合真相
- Leptos island 只消费 Rust projection
- 树域与主编辑区共享同一份 page aggregate
它现在更接近于:
> **`Rust-aware + Convex-backed + 前端本地组装` 的混合态。**
因此,当前看到的这些“小问题”:
- 标题单一真源问题已经开始收口,但页面聚合仍未完全统一
- 页面宽度和主编辑区宽度看起来不是一套规则
- 页面设置大部分只改了壳层,没有真正进入主编辑器
并不是孤立 UI bug,而是同一个底层断层的表现:
> **树域 projection 与主编辑区 page runtime 之间,还缺一个统一的 page aggregate 真相层。**
---
## 2. 先给结论
### 2.1 当前不能说已经实现了 Rust 单一真源
虽然当前文档页已经:
- 默认使用页面内正式 `leptos-tiptap` island
- 不再依赖外部 iframe bridge 作为主编辑器
- 正文保存已经能按 `workspaceId/documentId` 绑定到正确页面
但以下事实仍然成立:
- 标题链路虽已开始与树域 canonical snapshot 对齐,但还没有和正文、页面设置统一成同一组 page aggregate command family
- 页面正文是一条独立保存链
- 页面设置又是一条独立更新链
- 页面树 / 文件树与页头标题的一致性已明显改善,但当前 page aggregate projection 仍主要由前端装配而不是 Rust 原生提供
- `pageOptions` 已部分进入 `leptos-tiptap` 运行时语义层,但还没有整体收口完成
所以当前不能把这条线描述成:
> **“主编辑区、页面树、文件树已经统一为 Rust 下唯一真源”。**
更准确的表述应该是:
> **树域已经在向 Rust canonical contract 靠拢,但文档页主编辑区仍处于页面聚合尚未完成的混合切流阶段。**
### 2.2 当前最该收口的不是换编辑器,而是统一页面聚合边界
当前不该重新争论:
- 要不要 `Tiptap`
- 要不要 `leptos-tiptap`
- 要不要继续保留 Convex
这些结论都已经足够明确:
- `Tiptap` 继续作为浏览器输入 runtime
- `leptos-tiptap` 继续作为 Leptos 内正式 editor island 接缝
- `Convex` 继续作为当前存储 / 实时 / 协作底座
当前真正要收口的是:
> **页面这一层,到底由谁持有聚合语义,前端到底应该消费什么,编辑器到底应该回发什么。**
### 2.3 后续主线应固定为 Page Aggregate,而不是继续零散补洞
从长期架构看,当前主线不应再描述成:
- “继续补几个 `leptos-tiptap` 细节”
- “再把标题同步修一下”
- “再把页面设置接一点进去”
后续主线应改写成:
> **建立 Rust 主导的 `page aggregate`:让页面树 / 文件树 / 页面头部 / 页面设置 / 主编辑区 / AI 写入,都围绕同一组 page projection 与 page command family 运转。**
---
## 3. 当前问题的精确判断
## 3.1 当前页面数据并不是一个统一聚合
当前文档页在加载时,实际上是把几类数据拆开读取,再由前端壳层重新拼装:
- 页面 meta
- 页面标题
- 页面设置
- 页面正文
- 页面子树快照
这意味着当前不是:
- Rust 返回一份稳定的 page aggregate projection
而是:
- Next route 先查 meta
- 再查 content
- 再把 `title/options/content/pageSubtree` 手动组成页面 props
这条链本身已经说明:
> **当前页面不是消费一份 canonical page projection,而是消费几份分裂的子结果。**
### 3.1.1 标题链已开始收口,但尚未完成页面聚合统一
当前页头标题已经不再单纯由 `DocumentContent` 内部的长期本地 `pageTitle` 真相驱动,而是开始依赖与 sidebar / breadcrumb 共享的 preferred sidebar snapshot。
这带来两个直接后果:
1. 标题不同步这一类“前端本地状态长期漂移”问题已经明显减少
2. 但标题仍然没有和正文、页面设置一起统一到单一 page aggregate command / projection 边界中
于是:
- 页头、breadcrumb、sidebar、page tree、file tree 的标题一致性已有一轮真实收口
- 但从聚合语义上看,标题仍是一条相对独立的更新链,不等于 page aggregate 已经完成
这类问题不是“防抖没配好”,而是:
> **标题更新仍然是页面聚合之外的一条独立 side effect 链。**
### 3.1.2 正文和页面树仍然不是同一份 projection family
当前正文编辑器使用的是:
- `leptos-tiptap` runtime
- `/api/documents/save`
- `EditorBlockDocument / Tiptap / blocks` 的转换边界
而页面子树 / outline / evidence 使用的是:
- `pageSubtree`
- 前端本地定义的 TS 结构
- “内容与标题未变化时复用旧快照”的策略
这说明当前主编辑区与树域虽然已经能同时工作,但它们仍然不是:
- 同一个 Rust projection family 的不同视图
而是:
- 一边是编辑器保存链
- 一边是页面子树附带快照
因此当前 `pageSubtree` 更像:
- 页面阅读态和 TOC 的辅助投影
而不是:
- 主编辑区与树域共享的 canonical page aggregate
### 3.1.3 页面设置已部分进入 island,但还不是完整 editor runtime 语义
当前页面设置里至少有三类配置:
#### A. 页面壳层布局类
- `wideLayout`
- `smallText`
- `layoutDensity`
#### B. 页面视图类
- `showToc`
- `collapseBacklinks`
- `hideChildPages`
#### C. 编辑器语义类
- `showHeadingNumbers`
- `showBlockRefCount`
- `embedDefaultBlockId`
现在的主要问题不是“这些字段没有存下来”,而是:
> **它们虽然能持久化到 documents 表,也已有一部分进入 `leptos-tiptap` island,但还没有统一进入正式 page aggregate,更没有完整地进入 editor runtime 语义层。**
结果就是:
- 一部分设置已经作用于 island 布局或排版
- 一部分设置只影响阅读态
- 一部分设置被展示出来,但主编辑器内部几乎不消费
这也是为什么当前会出现:
> **页面设置看起来像是真的,但很多只是 UI 层局部生效。**
### 3.1.4 所谓“Rust 路径”里仍有明显 compat 痕迹
当前 rename / move / archive 等页面操作,虽然已经开始声明:
- `preferredCommandName`
但真实落地仍大量停留在:
- `documents.title.update`
- `documents.move`
- `documents.delete`
这说明当前并不能说:
- 树域命令已经完整切到 Rust tree command family
更不能说:
- 页面编辑域已经和树域命令收敛到同一个 page aggregate contract
因此现状应判定为:
> **Rust bridge 已经进入主链,但 page aggregate command cutover 仍未完成。**
---
## 4. 为什么这些问题会直接影响长期方向
## 4.1 它会削弱 Rust + Leptos 的真正优势
你们要的不是:
- “一个能跑的 Tiptap 页面”
而是:
- Rust 掌握页面与树的真相
- Leptos 成为页面内正式 editor island
- AI 最终直接接入主编辑区写作
如果标题、正文、设置、树节点标题分别走不同链路,那么 Rust + Leptos 的长期优势就会被削弱成:
- 只是“比纯前端多了一层 bridge”
而不是:
- “由 Rust 持有统一对象语义,前端只消费真相投影”
### 4.1.1 AI 直接写主编辑区会缺乏稳定入口
如果后续 AI 要直接进入主编辑区编写,而当前系统没有统一的 page aggregate command family,那么 AI 最终只能选下面几条坏路:
- 直接操作前端 DOM
- 直接操作 Tiptap JSON
- 直接调用不同的标题 / 正文 / 设置接口拼写入
这三条路都不符合长期目标。
长期正确路线必须是:
> **AI 先操作 Rust page aggregate 的命令与块语义,再由 editor island 把结果投影到 live editing surface。**
### 4.1.2 树域与页面域会继续出现“双真相漂移”
只要页面树 / 文件树消费的还是一套资源 meta,而主编辑区头部和页面设置是另一套前端状态,那么后续还会不断出现:
- 标题单一真源已开始收口,但当前尚未和正文、页面设置统一成同一 page aggregate 命令边界
- 页面设置变更只影响一部分视图
- 阅读态与编辑态宽度规则不一致
- 新增一个页面能力时,要改三四条链路
这类问题越到后面越难收。
---
## 5. 正确的目标重写
当前阶段的目标不应再写成:
- “把 `leptos-tiptap` 再打磨得更像官方模板”
而应重写为:
> **建立 Rust 主导的 `page aggregate`,让页面树 / 文件树 / 页面头部 / 页面设置 / 主编辑区 / AI 写入共享同一份页面真相。**
这句话拆开后,意味着下面五点。
### 5.1 Page Aggregate Projection
Rust 侧需要提供一个统一的页面聚合投影,至少包含:
- `page_identity`
- `workspace_id`
- `document_id`
- `node_id`
- `page_head`
- `title`
- `icon`
- `cover`
- `updated_at`
- `permissions`
- `page_layout`
- `wide_layout`
- `small_text`
- `layout_density`
- `show_toc`
- `show_structure`
- `page_body`
- `editor_document`
- `revision`
- `conflict_detection_key`
- `stats`
- `page_tree`
- `outline`
- `child_pages`
- `resource_meta`
当前不要求一次把所有字段做满,但必须先把口径冻结。
### 5.2 Page Command Family
后续页面域的操作不应继续被拆成互不统属的零散 mutation,而应在语义上统一到 page aggregate 之下。
最小命令族至少包括:
- `page.head.rename`
- `page.layout.update`
- `page.body.save`
- `page.body.insert_reference`
- `page.body.set_embed_default_block`
- `page.tree.refresh_projection`
这不意味着现在立刻把所有接口名都改掉,而是:
> **从现在开始,所有页面能力都应先判断它属于哪个 page aggregate 子域,而不是继续新增离散 mutation。**
### 5.3 Leptos Island 的正式边界
`leptos-tiptap` island 的职责应收敛为:
- 消费 `page_body`
- 消费必要的 `page_layout` 编辑器相关配置
- 回发正文与块级命令
- 暴露可供 AI / 页面壳使用的稳定 editor handle
它不应再负责:
- 定义页面总真相
- 自己发明页面设置语义
- 在页面壳之外另持有一套页面元信息状态
### 5.4 Tree Domain 的接缝
页面树 / 文件树不需要知道编辑器内部细节,但必须与页面聚合共享:
- 同一份 `document_id`
- 同一份页面标题
- 同一份 `resource_meta`
- 同一份页面可见状态与能力语义
也就是说:
- 树域保持 tree-first
- 页面域保持 page aggregate
- 两者通过稳定 projection contract 对齐
而不是:
- 树域一套标题来源
- 页面域另一套标题来源
### 5.5 Convex 的正确定位
当前不应该先拆 Convex。
正确口径是:
- Convex 继续作为存储 / 实时 / 协作底座
- Rust 持有 canonical contract 与语义编排
- Leptos / Next 负责消费 projection 与呈现 island
因此,“Rust 单一真源”在当前阶段的正确含义不是:
- 只有 Rust 数据库
而是:
- **Rust 持有语义单一真源,Convex 持有当前持久化底座。**
---
## 6. 当前哪些地方需要纠偏
## 6.1 需要纠偏的不是“页面里还有前端状态”
页面壳中存在一些本地临时状态本身没有问题。
问题在于:
- 哪些状态是临时 UI 状态
- 哪些状态却在冒充对象真相
后续必须把两者分清。
### 6.1.1 可以继续留在前端壳的状态
- inspector 开关
- comments drawer 开关
- history drawer 开关
- 当前是否在编辑态
- 当前 host observability
这些都是 UI state,不必进入 Rust 真相层。
### 6.1.2 必须退出前端壳真相地位的状态
- 页面标题
- 页面宽度 / 页面布局正式选项
- 编辑器默认嵌入位置
- 页面 outline 的正式来源
- 页面 stats 的正式来源
这些都不应继续被前端 `useState` 长期定义为页面事实。
## 6.2 需要纠偏的不是继续换保存底座
当前正文保存链已经基本可用,真正的问题不是“保存不到页面”,而是:
- 保存成功不等于 page aggregate 已建立
因此:
- 现在不该推翻 `documents.save`
- 也不该另造一套临时保存格式
而应做的是:
> **把标题 / 正文 / 页面设置的语义边界对齐到同一 page aggregate 之下。**
## 6.3 需要纠偏的是页面设置的产品口径
当前页面设置里已经混入了三类字段:
- 真正属于页面布局的
- 真正属于阅读壳的
- 真正属于编辑器 runtime 的
后续必须先分类,再决定哪些继续保留在页面设置面板里,哪些推迟支持,哪些进入 editor island。
否则只会继续出现:
- UI 有按钮
- 数据能存
- 但主编辑器不真正消费
---
## 7. 分阶段落地
下面的阶段不是完整开发清单,而是主线纠偏顺序。
## 7.1 Phase F: 冻结 Page Aggregate Contract
目标:
- 在设计层明确页面聚合的 canonical contract
- 不再让页面页头 / 正文 / 设置 / 子树各自长字段
完成标准:
- 有单独的 page aggregate contract 文档
- 明确 `page_head / page_layout / page_body / page_tree` 的最小字段
- 明确哪些字段是 UI state,哪些字段是 aggregate truth
## 7.2 Phase G: 主文档页改为消费统一聚合
目标:
- 文档页不再手工拼 `title + options + content + pageSubtree`
- 改为消费统一页面聚合返回值
完成标准:
- 页面头部使用聚合中的 `page_head`
- 页面设置初始值使用聚合中的 `page_layout`
- 主编辑区 bootstrap 使用聚合中的 `page_body`
- 树与 TOC 使用聚合中的 `page_tree`
## 7.3 Phase H: `leptos-tiptap` 正式消费 editor-related page options
目标:
- 把真正属于编辑器 runtime 的设置接入 `leptos-tiptap`
最小优先级建议:
1. `wideLayout`
2. `smallText`
3. `layoutDensity`
4. `showHeadingNumbers`
5. `embedDefaultBlockId`
完成标准:
- inspector 改动后,主编辑区表现真实变化
- 不再出现“设置能存,但编辑器内部几乎没变化”
## 7.4 Phase I: 树域与页面域标题统一
目标:
- rename 后页头、sidebar row、page tree、file tree 一致刷新
完成标准:
- 标题变更只经过一套 page aggregate command 语义
- 所有标题消费者都来自同一份更新后的 projection
## 7.5 Phase J: AI 写入入口对齐 page aggregate
目标:
- AI 不再绕过 page aggregate 直接拼前端对象
完成标准:
- AI 能通过统一页面命令把内容写入主编辑区
- 人工编辑与 AI 编辑共享同一块语义边界
---
## 8. 当前阶段的完成判定
只有当下面这些条件同时成立时,才能把这条线描述成:
> **“主编辑区与树域已经基本统一到 Rust 主导的单一真源架构”。**
- 文档页消费统一 `page aggregate projection`
- 标题 / 正文 / 页面设置不再是三条分裂真相链
- `leptos-tiptap` island 真正消费 editor-related `page_layout`
- 树标题与页头标题来自同一份 projection
- AI 写入入口开始围绕 page aggregate command family 设计
在此之前,正确口径都应保持为:
> **`leptos-tiptap` 主编辑区已基本可用,但页面聚合仍未收口,当前仍处于从混合态向 Rust 单一真源过渡的过程中。**
---
## 9. 本文结论
这次暴露出的页面宽度不统一、页面设置只部分生效,以及标题链虽已收口但聚合仍未统一,并不是坏消息。
它们的价值在于:
> **它们准确暴露了当前真正缺的不是“再修几个细节”,而是“把页面域提升成一个与树域对齐的 Rust page aggregate”。**
因此,后续主线不应继续散落成:
- 修标题
- 修宽度
- 修页面设置
而应统一收口为:
> **Page Aggregate Contract**
>
> **Page Aggregate Projection**
>
> **Page Aggregate Command Family**
这才是让 `tree-first graph kernel``leptos-tiptap` 主编辑区、以及后续 AI 直接写入主编辑区三者真正对齐的正确底层方向。
@@ -0,0 +1,269 @@
# 5-6 [process] Page Aggregate 单一真源对齐执行清单 v1
> 更新时间:2026-04-22
>
> 关联文档:
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-5-page-aggregate-single-truth-alignment-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-4-tree-projection-protocol-contract-v1.md`
## 1. 文档目的
这份清单不是再讨论“问题是不是存在”,而是把:
- `5-5` 里对当前混合态的判断
- 后续 `Page Aggregate` 主线
整理成一份能持续勾选、持续验收、持续防止跑偏的执行清单。
这份清单只围绕一个目标:
> **让页面树 / 文件树 / 页面头部 / 页面设置 / 主编辑区 / AI 写入口,逐步收口到 Rust 主导的 page aggregate 单一真源。**
---
## 2. 当前阶段结论
当前可以确认的事实有两类。
### 2.1 已经成立的事实
- [x] `leptos-tiptap` 已经成为页面内正式主编辑区,不再是 iframe bridge。
- [x] 正文保存已经能按 `workspaceId/documentId` 正确落到对应页面。
- [x] 当前主编辑区已具备可继续推进的基础交互能力。
- [x] 当前问题已经不再是“能不能接入主编辑器”,而是“接入后如何收口为单一真源”。
### 2.2 还没有成立的事实
- [ ] 页面树 / 文件树 / 页面头部 / 页面设置 / 主编辑区还没有消费同一份 page aggregate projection。
- [x] 标题 / 正文 / 页面设置已经开始统一到同一组 page aggregate command family。
- [ ] `pageOptions` 还没有整体收口到 `leptos-tiptap` island 的正式运行时语义层。
- [ ] AI 写入口还没有完整对齐 page aggregate command family。
补充:本轮已新增前端统一 `page-command-client`,并把 `DocumentContent` 的标题 / 页面设置写入、AI 正文写回、以及 `BlockNote` / `leptos-tiptap` 各 host 的正文保存统一到 `page.head.updateTitle / page.layout.updateOptions / page.body.save`。同时,Next route 侧已新增统一 `page-write-command-adapter``/api/documents/title``/api/documents/options``/api/documents/save` 三条页面写链路已开始共用同一层执行器。这里勾选的是“命令执行面开始收口”,不等于页面域真相链已经完全统一。
补充:2026-04-28 树域剩余 runtime 收口中,`page.body.saved``document.snapshot.saved` 已完成职责拆分;`tree.node.embed``pageReference` block 结构与插入位置已由 Rust `pageAggregateEmbedPlan` 生成,3000 route 只保留源/目标内容读取作为 substrate preflight。这使正文保存快照语义和页面嵌入正文 patch 都开始落在 Page Aggregate / Rust artifact 边界内。
---
## 3. 当前明确不做什么
为了避免再次回到“零散补 UI”的老路,下面这些项当前不进入主线前排:
- [ ] 不把当前问题重新降级成“修两个样式 bug”。
- [ ] 不继续通过新增前端本地状态来掩盖 page aggregate 缺失。
- [ ] 不先重做一轮页面设置 UI。
- [ ] 不先扩一批和 page aggregate 无关的编辑器花活。
- [ ] 不先争论替换 `Tiptap`、替换 `leptos-tiptap`、移除 Convex。
---
## 4. Phase F:冻结 Page Aggregate Contract
目标:
> **先把页面聚合的 canonical contract 写清楚,阻止页面头部、正文、设置、子树继续各自长字段。**
### 4.1 设计层完成标准
- [x] 单独补一份 page aggregate contract 文档。
- [x] 文档中固定 `page_identity / page_head / page_layout / page_body / page_tree` 的最小字段。
- [x] 文档中固定哪些字段属于 page aggregate truth,哪些字段只是前端 UI state。
- [x] 文档中固定哪些字段必须由 Rust 主导,哪些字段允许作为前端临时态存在。
### 4.2 代码层完成标准
- [x] 前端与 Rust 侧都出现统一命名的 page aggregate 类型或等价契约。
- [x] 不再继续给 `DocumentPageProps``DocumentContentProps` 零散加字段来扩页面真相。
- [x] 文档页加载入口能明确区分“聚合读取结果”和“局部 UI 临时态”。
补充:当前已新增 `page-aggregate-builder.ts``page-aggregate-loader.ts``/api/documents/page`,文档页 SSR 入口和 `DocumentContent` 的内容重试补拉都已改为消费同一份 `PageAggregateProjection`,不再由 `page.tsx` 手工拼 `meta + content`。Rust 侧仍未直接暴露同名 `Page Aggregate` projection route,但 `storage-convex-bridge``bridge-runtime` 已开始接受 `page.head.updateTitle / page.layout.updateOptions / page.body.save` 这组 page command family 的命名口径,因此这里先按“等价契约已出现”勾选完成。
### 4.3 退出标准
- [x] 后续再讨论标题、页面设置、正文保存时,能够直接定位到它属于 `page_head / page_layout / page_body / page_tree` 的哪一层。
---
## 5. Phase G:主文档页改为消费统一聚合
目标:
> **让 `/documents/[id]` 不再手工拼 `title + options + content + pageSubtree`,而是消费统一页面聚合。**
### 5.1 加载链路收口
- [x] 页面 SSR / route 入口改为优先读取统一 page aggregate,而不是分别读取 meta 和 content。
- [x] `DocumentShell` / `DocumentContent` 的入参改为围绕统一聚合对象组织。
- [x] 页面头部、页面设置初值、主编辑区 bootstrap、TOC/outline 都来自同一份聚合结果。
### 5.2 当前散落 props 的收口
- [x] `title` 不再作为独立真相字段散落传递。
- [x] `initialOptions` 不再作为独立真相字段散落传递。
- [x] `initialContent``initialRevision``initialConflictDetectionKey` 归入统一 `page_body`
- [x] `initialPageSubtree` 归入统一 `page_tree`,并明确其是正式 projection 还是仅兼容过渡项。
### 5.3 退出标准
- [x] 页面加载时不再能明显看出“这是几份子结果拼出来的页面”。
- [ ] 后续页面新增字段时,不再需要继续向外层 props 链同时塞多种局部真相。
补充:当前首屏 SSR 与客户端内容重试补拉都已经走 `/api/documents/page -> page aggregate loader -> PageAggregateProjection`,因此“页面明显由 `meta + content` 两次查询拼起来”的入口级痕迹已经消失;但新增字段仍可能需要继续补 loader / route / island 消费链,所以第二项继续保留未完成。
补充:本轮已新增 `page-aggregate-client-state.ts`,并让 `DocumentContent` 把原先分散维护的 `options / content / serverContentSnapshot / serverPageSubtreeSnapshot / serverPageSubtreeTitle / contentRevision / conflictDetectionKey` 开始收口为同一份 client aggregate state reducer。这里代表“页面本地 `body/layout/tree` 真相已经开始统一”,不等于标题链、页面设置运行时分类、AI 对页面设置面的正式入口都已闭环,因此本阶段不提前宣称聚合完成。对应最小回归测试为 `page-aggregate-client-state.test.ts`
---
## 6. Phase H:让 `pageOptions` 真正进入 `leptos-tiptap` island
目标:
> **把当前“能展示、能保存、但主编辑区不真正消费”的页面设置,按优先级接入 island。**
### 6.1 必须先分类
- [x] 把当前页面设置分成:页面壳布局类、阅读视图类、编辑器 runtime 语义类。
- [ ] 明确哪些项应继续保留在页面设置面板中,哪些项应降级或隐藏,哪些项必须进入 island。
补充:本轮已新增 `page-option-semantics.ts`,把 `pageOptions` 的运行时语义正式收口为代码契约,不再让这套规则继续散落在 `page-options-sidebar.tsx``editor-host-types.ts``leptos-tiptap-island-editor-host.tsx` 里各写一份。当前至少已经明确:
- `wideLayout / smallText / layoutDensity` 属于 `page_shell_layout + editor_runtime`
- `showHeadingNumbers` 属于 `read_view + editor_runtime`
- `showToc` 属于 `read_view`
- `protectEditing / showBlockRefCount` 属于 `editor_runtime`,但当前仍是 `planned`
- `embedDefaultBlockId` 已进入 `editor_runtime`
对应最小回归测试为 `page-option-semantics.test.ts``leptos-tiptap-island-editor-host.test.tsx``page-options-sidebar.test.tsx`。第二项继续保留未完成,因为“哪些项应降级/隐藏/保留”的产品面纠偏还没完全落到 inspector 与 AI tool surface。
### 6.2 最小接入优先级
- [x] `wideLayout` 真正进入 island 布局语义,而不是只改外层壳宽度。
- [x] `smallText` 真正进入主编辑区排版语义。
- [x] `layoutDensity` 真正进入块间距 / 正文密度语义。
- [x] `showHeadingNumbers` 真正进入 heading 展示语义,或明确标注暂不支持。
- [x] `embedDefaultBlockId` 真正进入主编辑器引用 / 嵌入默认位置逻辑,或明确标注暂不支持。
### 6.3 页面设置面板纠偏
- [x] 没有真正接入 island 的编辑器语义项,不再继续以“已开启/已关闭”假装正式完成。
- [x] 对未支持项给出显式降级说明,而不是仅保存字段。
- [ ] 让“页面设置有值但编辑器内部没变化”这类状态在产品上消失。
### 6.4 退出标准
- [ ] inspector 中至少最小优先级项修改后,主编辑区可见结果真实变化。
- [ ] 用户不再需要猜“这个设置到底有没有真正作用到编辑器”。
---
## 7. Phase I:树域与页面域标题统一
目标:
> **rename 后页头、sidebar row、page tree、file tree 使用同一份标题结果。**
### 7.1 标题真相纠偏
- [x] 页面头部标题不再长期由前端 `pageTitle` 本地状态冒充正式真相。
- [x] rename 命令在语义上进入统一 page aggregate command family。
- [x] 树域消费的标题与页面头部消费的标题来自同一份更新后的 projection。
### 7.2 回归验证
- [x] 当前页重命名后,页头即时更新。
- [x] sidebar 对应节点标题同步更新。
- [x] page tree / file tree 对应节点标题同步更新。
- [x] 刷新后标题一致,不依赖额外手动刷新。
- [x] 切页往返后标题一致,不出现旧快照回闪。
注:当前已在标题持久化成功后广播 `emitDocumentsChanged(documentId)`,并补了 `sidebar-events.test.ts` 覆盖 `documents-changed -> sidebarRefetch` 的刷新链路。
补充:已新增 `usePreferredSidebarSnapshot`,当 `tree stream` 已连上但快照落后、`documents-changed` 触发的 query/refetch 先拿到新标题时,会优先消费更新后的 canonical sidebar snapshot;待 stream 追平后再回到 live stream。对应回归测试为 `use-preferred-sidebar-snapshot.test.tsx`。这修掉了“刷新链发出去了,但 stale stream 仍把 sidebar 标题压回旧值”的一类问题;浏览器层 `sidebar row / page tree / file tree` 真实渲染验收仍待补齐。
补充:已新增 `AppLayoutShell`,把 layout 顶栏 `Breadcrumb` 从 SSR 注入的静态 `documents` 挪到与 `Sidebar` 共享的同一条 live sidebar snapshot 管线;并且 layout shell 会把“已选中的 preferred snapshot”同一对象同时透传给 `Sidebar``Breadcrumb`,不再各自独立选择。对应回归测试为 `app-layout-shell.test.tsx`。这意味着 breadcrumb / sidebar 现在至少共享同一份工作区树 canonical snapshot,不再是 layout 一条静态链、sidebar 一条 live 链并行。页头 `page.head.title` 与这条工作区树链之间的最终统一验收仍待补齐,因此本阶段继续不提前打满。
补充:已新增 `PreferredSidebarSnapshotProvider``usePageHeadTitle`,把文档页头标题从 `DocumentContent` 内部长期持有的 `pageTitle` 本地真相,改为“同一份 preferred sidebar snapshot 的 committed title + 短暂 draft”。同时修正 `useSidebarData.refetch()`,在 Convex live 模式下收到 `documents-changed` 也会主动拉取一份新的 `/api/sidebar` snapshot,再与 tree stream 做 freshness 选择,避免“页头草稿是新的,但 breadcrumb / sidebar / page tree / file tree 还卡在旧快照”。对应单测为 `use-page-head-title.test.tsx``use-sidebar-data.test.tsx`。浏览器烟测 `scripts/task110-page-title-single-truth-smoke.js` 已验证:页头重命名后,breadcrumb、默认 sidebar、page tree、file tree、切页往返、刷新均保持一致,因此本阶段与“标题来自同一份更新后的 projection”相关的勾选正式保留。
### 7.3 退出标准
- [x] 标题不同步问题不再以“前端本地状态漂移”的形式重复出现。
---
## 8. Phase JAI 写入口对齐 Page Aggregate
目标:
> **AI 不再绕过 page aggregate,直接拼前端对象或编辑器内部格式。**
### 8.1 语义边界
- [x] 明确 AI 写页面时优先进入哪组 page aggregate command。
- [x] 明确 AI 写标题、写正文、改页面设置、插入引用时分别走哪些统一命令语义。
- [x] 明确 AI 不直接拼 DOM、不直接拼前端页面壳对象、不直接依赖浏览器临时状态。
补充:当前页面命令名已开始从 `documents.*` 收口到 `page.head.updateTitle / page.layout.updateOptions / page.body.save`,其中标题 route、页面设置 route、正文保存 route 都已切到这组 page command family`storage-convex-bridge``bridge-runtime` 也已补齐对应 alias。
补充:本轮进一步把前端调用面与 Next route 执行面也开始统一到这组 `page.*` family:前端已新增 `page-command-client`,不再让 `DocumentContent`、AI 面板、各编辑器 host 各自维护一套页面写入口;服务端已新增 `page-write-command-adapter``title/options/save` 三条 route 不再分裂在 `metadata/save` 两套执行器里。对应最小回归测试为 `page-command-client.test.ts``page-write-command-adapter.test.ts``route-adapters.test.ts``DocumentAiAgentPanel.runtime.test.tsx``document-content.test.ts`
补充:本轮继续把 AI 面板的读取边界开始收口到统一页面本地快照:`DocumentContent` 不再向 `DocumentAiAgentPanel` 透传 `getLatestBlocks / getLatestPageSubtree / getLatestPersistedMeta` 三组分散 getter,而是改为一份 `getLatestPageAggregateSnapshot`,由 `page-aggregate-client-state` 导出 `blocks / pageSubtree / persistedMeta / pageOptions`。这代表 AI 面板已开始消费同一份页面本地 aggregate state,而不是继续拼接独立局部真相;但页面设置写工具本身仍未进入 Hermes 正式 tool surface,因此这里仍然只算“开始收口”,不提前打满。
补充:本轮还把 `pageOptions``editorRuntimePageOptions` 一并带入 `/api/ai-agent/run -> buildHermesInstructions`,因此 AI 在服务端至少能看到“当前页面设置是什么”以及“哪些设置已经进入 island runtime payload”,不再只依赖正文块快照和树快照来猜测页面语义。对应回归测试为 `src/app/api/ai-agent/run/route.test.ts`。这推进的是 AI 读侧上下文,不等于页面设置写工具已经具备正式入口。
### 8.2 与主编辑区的关系
- [x] 人类编辑与 AI 编辑共享同一块语义边界。
- [x] AI 改写结果能通过主编辑区 island 正式回显。
- [ ] AI 改写后树标题 / 页面头部 / 页面设置不再走各自独立副作用链。
### 8.3 退出标准
- [x] AI 写入口已经可以被明确描述为“操作 page aggregate command family”,而不是“绕过系统写编辑器”。
注:当前 `/api/ai-agent/run``Hermes tool.completed` 后,会优先尝试把 `slash_run / doc_insert_blocks / doc_replace_range` 恢复成 `mnote-web bridge-runtime` 的结构化 `tool_result`,不再只把 Hermes 事件当作薄日志。其后:
- `doc_insert_blocks / doc_replace_range` 继续按 `page.body.save` 语义落到 `/api/documents/save`,再正式回显主编辑区 island。
- `slash_run(rename current page)` 会把结构化结果回接到当前页 `DocumentContent` 的同一条标题提交链,并继续广播 `emitDocumentsChanged(documentId)`,因此页头标题与树标题不再靠 AI 面板内部本地状态各自漂移。
- 当前 `pageOptions` 仍没有进入 Hermes 正式 tool surface,因此“页面设置类 AI 命令”尚未收口;`8.2` 的最后一项继续保留未完成,避免误判为整条线已经闭环。
---
## 9. 推荐实施顺序
当前推荐顺序固定为:
1. `Phase F`
2. `Phase G`
3. `Phase H`
4. `Phase I`
5. `Phase J`
约束如下:
- [x] 没有完成 `Phase F` 之前,不再继续零散补标题 / 宽度 / 页面设置。
- [ ] 没有完成 `Phase G` 之前,不把当前文档页描述成“已经统一聚合完成”。
- [ ] 没有完成 `Phase H` 之前,不把页面设置大量打钩为“正式可用”。
- [ ] 没有完成 `Phase I` 之前,不把标题问题视为已从底层解决。
- [ ] 没有完成 `Phase J` 之前,不把 AI 直接写主编辑区描述成已经具备正式稳定入口。
---
## 10. 当前阶段的总退出标准
只有当下面这些条件同时成立时,才可以把这条线描述成:
> **“主编辑区与树域已经基本统一到 Rust 主导的 page aggregate 单一真源架构”。**
- [x] 文档页消费统一 `page aggregate projection`
- [ ] 标题 / 正文 / 页面设置不再是三条分裂真相链
- [x] `leptos-tiptap` island 真正消费 editor-related `page_layout`
- [x] 树标题与页头标题来自同一份 projection
- [x] AI 写入口开始围绕 page aggregate command family 实现
补充:当前未勾选“标题 / 正文 / 页面设置不再是三条分裂真相链”的原因,不再只是命令名或 route 分裂。虽然前端页面写入口、Next route 执行器、`DocumentContent` 内部的 `body/layout/tree` client state、以及 `pageOptions` 的代码级运行时分类都已经开始收口,但 projection 回流与 AI 对页面设置面的正式写入口仍未完全统一。也就是说,命令执行面、页面本地状态面、页面设置语义面都已开始统一,但页面域单一真源仍未闭环。
在此之前,正确口径都应保持为:
> **`leptos-tiptap` 主编辑区已基本可用,但页面聚合仍未收口,当前仍处于从混合态向 Rust 单一真源过渡的过程中。**
@@ -0,0 +1,935 @@
# 5-7 [process] Wolai 页面树与主编辑器体验复刻方案 v1
> 更新时间:2026-04-30
>
> **Wolai-aline 执行口径更新(2026-04-30):** 本文只保留体验复刻任务拆解和产品目标。所有 Wolai 对标测试、浏览器取证、编辑权限、安全边界、subagent 使用、截图复核和 smoke 补齐,统一以 `/home/lix/.codex/skills/wolai-aline` 与 `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md` 为准。若本文旧段落与该 skill 冲突,以 skill 为准。
>
> 关联文档:
> - `/mnt/Data1T/mnote/ARCHITECTURE.md`
> - `/mnt/Data1T/mnote/design/01-05-current-priority-overview.md`
> - `/mnt/Data1T/mnote/design/01-tree-first-graph-kernel/process/1-tree-first-graph-kernel-v1.md`
> - `/mnt/Data1T/mnote/design/02-convex-rust-long-term-architecture/process/2-tree-first-graph-convex-rust-long-term-architecture-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-rust-web-long-term-architecture-v1.md`
> - `/mnt/Data1T/mnote/design/03-rust-web/process/3-3-rust-web-tree-realtime-event-stream-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-2-sidebar-pagetree-filetree-product-interaction-contract-v1.md`
> - `/mnt/Data1T/mnote/design/04-tree-domain/done/4-6-tree-command-protocol-cutover-stage2-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-2-tiptap-notion-like-template-adoption-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-4-leptos-tiptap-mainline-correction-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/process/5-6-page-aggregate-alignment-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`
> - `/mnt/Data1T/mnote/design/05-editor-mainline/done/5-5-1-page-aggregate-contract-v1.md`
## 1. 文档目的
这份方案只处理一件事:
> **以真实 Wolai 页面为视觉和交互 benchmark,继续还原 mnote 的页面树与主编辑器体验。**
本轮目标不是重新定义系统架构,也不是把 Wolai 的全部协作能力完整复制过来,而是把当前 `3000` 页面在视觉密度、布局节奏、页面树反馈、主编辑器输入体验上继续向 Wolai 靠近。
约束如下:
- UI 以像素级复刻为目标。
- 功能以“高频路径可用、复杂能力可降级”为原则。
- 当前项目保留“文件树 / Explorer”创新,不要求删除。
- 默认主编辑器继续是页面内 `leptos-tiptap` island。
- `BlockNote` 只作为 fallback / 对照链,不重新变成默认方向。
- 树、页面结构、标题、正文、页面设置不能在 UI 层再拼第二份真相。
这份文档应放在 `05-editor-mainline/process`,因为它的核心不是 Rust Web 壳复刻,也不是单独的树命令合同,而是:
> **围绕 Page Aggregate,把页面树、页头、页面设置、正文编辑器统一成接近 Wolai 的产品体验。**
---
## 2. 本轮取证基线
### 2.1 真实 Wolai 参考
本轮参考页面:
- `https://www.wolai.com/wolai/xhqeop8UHpVTMUSVmgz8nq`
- `https://www.wolai.com/wolai/qN1Bh9YjLAXs8bxCJoAJ6C`
- `https://www.wolai.com/liaibo/ikFSSM1a4GgvmHBYFCNfVd`
本轮已保存的截图与快照:
- `/mnt/Data1T/mnote/tmp/wolai-page1.png`
- `/mnt/Data1T/mnote/tmp/wolai-page1-snapshot.md`
- `/mnt/Data1T/mnote/tmp/wolai-page2.png`
- `/mnt/Data1T/mnote/tmp/wolai-page2-snapshot.md`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-target-1392x1213.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-target-snapshot.md`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-search-overlay.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-search-results-skill.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-presentation-mode.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-start-edit-overlay.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-comment-panel.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-public-good-night-mode.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-first-viewport-current.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-sidebar-filter-page-reference.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-search-overlay.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-search-results-page-reference.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-search-result-navigated-block-reference.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-embedded-page-reference-hover.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/wolai-help-block-reference-page-snapshot.md`
- `/mnt/Data1T/mnote/wolai-basic-editing-reference.png`
- `/mnt/Data1T/mnote/wolai-basic-editing-reference-snapshot.md`
- `/mnt/Data1T/mnote/wolai-hermes-public-hover-block.png`
- `/mnt/Data1T/mnote/wolai-hermes-start-edit-login-dialog.png`
- `/mnt/Data1T/mnote/wolai-hermes-comment-panel.png`
- `/mnt/Data1T/mnote/wolai-hermes-presentation-mode.png`
- `/mnt/Data1T/mnote/wolai-hermes-good-night-mode.png`
用户在内置浏览器中提供的登录态截图也作为本轮重要视觉证据:该截图展示了真实个人空间的长页面树、当前页 `Hermes` 选中态、完整顶部图标区、底部垃圾桶 / 模板中心入口、右下角 AI / 帮助浮动入口。
补充说明:历史截图和用户提供的登录态截图只作为现有证据池,不再作为后续任务的唯一验收依据。该 owner 态菜单不是单纯样式:它包含“在右侧边栏打开 / 移动到 / 嵌入到 / 复制访问链接 / 复制页面引用链接 / 复制页面 ID / 拷贝副本 / 重命名 / 删除”等页面树动作,应进入后续交互合同,而不是只当成截图复刻。
2026-04-30 口径更新:后续取证统一从 `wolai-aline` skill 启动。已知技术路径是 `Playwright Node + /opt/google/chrome/chrome + /mnt/Data1T/mnote/tmp/wolai-playwright-profile`,但本文不再维护独立自动化方案。当前 Hermes 测试页已授权用于最小编辑验证;owner 首屏、搜索浮层、顶部更多菜单、正文块 hover、`.hover-block-menu` 块菜单等行为,都必须通过 subagent 浏览器取证、主线程截图复核和差异矩阵重新确认。`dokobot doko read --local --reuse-tab` 可作为登录态 read evidence,但不能替代键鼠操作验收。
### 2.2 当前 mnote 参考
当前本地 `3000` 截图:
- `/mnt/Data1T/mnote/tmp/wolai-compare/local-3000-playwright-1392x1213.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/local-3000-first-viewport.png`
- `/mnt/Data1T/mnote/tmp/wolai-compare/local-3000-dom-snapshot.txt`
当前本地页面已经具备接近 Wolai 的基本壳:
- 左侧 workspace / 快捷操作 / 页面树 / 底部入口。
- 右侧顶部 breadcrumb / 页面操作。
- 中央文档页标题和正文区。
- 右下角帮助与 AI 入口。
但与真实 Wolai 仍有显著差距:
- 左侧树的视觉密度、选中态、图标系统、滚动条和层级缩进仍不够像 Wolai。
- 文档主内容的起始位置、标题尺寸、正文列宽、页头留白和默认元信息展示不一致。
- 顶栏按钮体系与 Wolai 的 owner / published 两种状态还没有明确分型。
- 主编辑器的块级 hover 手柄、slash、浮动工具条和块菜单需要按 Wolai / Notion 风格继续收口。
- 当前页面树、页头、正文、页面设置仍必须继续服从 Page Aggregate 单一真源主线,不能为了 UI 快速复刻重新制造局部状态。
### 2.3 2026-04-30 subagent 基线复核
本轮已按 `wolai-aline` skill 派 subagent 同时操作 Wolai Hermes 与本地 `3000`。工具链结论:`Node.js Playwright + /opt/google/chrome/chrome` 可操作两个目标;Wolai 通过 `/mnt/Data1T/mnote/tmp/wolai-playwright-profile` 进入 owner 登录态,本地 `3000` 返回可用页面。无源码改动,证据写入:
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/`
- `/mnt/Data1T/mnote/tmp/wolai-editor-parity/doc-baseline-mini/`
主线程已复核关键截图:
- Wolai 首屏:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/doc-baseline-mini/wolai-initial.png`
- 本地首屏:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/doc-baseline-mini/local-initial.png`
- Wolai 搜索:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-03-search-modal-controls-filled.png`
- 本地搜索:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/local-03-search-modal-controls-filled.png`
- Wolai 块 hover`/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-08-block-hover-insert-entry.png`
- 本地块 hover`/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/local-14-block-hover-insert-entry.png`
- Wolai 编辑清理后:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-14-title-suffix-after.png`
当前差异矩阵:
| 项 | Wolai Hermes | 本地 `3000` | 后续处理 |
| --- | --- | --- | --- |
| 首屏入口 | owner 登录态直接显示 `Hermes` 正文页,标题、正文、页面树同屏 | 默认是聚合 / 预览壳,正文编辑器需点击 `打开当前页面` 才出现 | 拆成 P0/P1 独立任务;先用 smoke 固化“普通文档首屏直接进入正文体验”或明确产品降级 |
| 侧栏数据与选中态 | 真实空间树,当前页在 `个人 / 软件开发 / Hermes` 路径下,红色选中态明显 | anonymous 示例树,当前页为 `1111`,行密度和图标体系仍偏本地化 | Sidebar checklist 必须同时验视觉和真实 active path |
| 搜索入口 | 左侧放大镜可打开全局搜索;本轮 `Ctrl+K` 未稳定打开,`Ctrl+P` 在另一轮可 toggle | 顶栏搜索可打开;侧栏搜索曾修为 modal;`Ctrl+P` 可 toggle | 搜索任务不得只测一个入口;需记录每个入口、快捷键和 URL |
| 搜索控件 | modal 居中,遮罩、结果行、快捷键提示完整;switch 视觉可见 | modal 结构接近,`role="switch"` 可测;不同输入下结果为空或本地数据结果 | switch 默认状态必须同时记录截图和 `aria-checked`,结果态需拆数据差异与控件差异 |
| 搜索结果 | 输入 `Hermes` 命中页面和块,结果有红色高亮和路径 | 本地同词可出现空态或示例数据结果,取决于当前页面数据 | 建立固定本地 fixture 或 smoke 种子,不能用随机工作区数据验视觉 |
| 正文编辑区 | 首屏正文 `contenteditable=true`,标题也可编辑 | 首屏无可见编辑区;点击后出现 title textarea 与 `.tiptap.ProseMirror` | 文档画布任务必须先消除“打开当前页面”入口语义差异或显式记录降级 |
| 编辑焦点风险 | Wolai 标题和正文均可编辑,测试输入曾误落到标题,已清理 | 本地输入 / 撤销可完成,但页面结构不同 | editable-test mode 前必须先断言焦点 block 类型,优先在正文末尾新建唯一测试块 |
| 块 hover | 正文块左侧出现轻量块控制入口,视觉很克制 | 本地显示 `+` 与拖拽/块句柄按钮,但位置、密度、内容列差异明显 | E2-E4 必须以截图和 hover 点位复核,不可只看按钮存在 |
这轮基线说明:当前最大偏差不是单个按钮,而是本地普通文档首屏仍像“聚合入口 / 预览页”,Wolai 则直接进入可读写的页面正文。后续 P0/P1 checklist 必须优先处理这个入口语义,否则搜索、块 hover、编辑器测试都会测到不同页面状态。
### 2.4 Wolai-aline 取证与验收统一口径
本节旧版“published / owner readonly / sandbox mutation”分层已经收口到 `wolai-aline` skill。后续执行不再从本文推导测试策略,统一遵循:
- Codex skill`/home/lix/.codex/skills/wolai-aline`
- 流程文档:`/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
- 失败模式:`/home/lix/.codex/skills/wolai-aline/references/failure-patterns.md`
现行要点:
- 每个 Wolai-aline 小任务都必须先取 Wolai 基线,再做本地 RED smoke,再实现,再由 subagent 浏览器复测,最后主线程复核截图和差异矩阵。
- 浏览器取证必须使用 subagent;subagent 默认只读,但当任务明确需要编辑器行为时,可在当前 Hermes 测试页进入 `Hermes editable-test mode` 做最小编辑验证。
- 当前 Hermes 测试页 `https://www.wolai.com/liaibo/ikFSSM1a4GgvmHBYFCNfVd` 已授权用于最小编辑验证;可以创建带唯一标记的临时测试块、输入少量测试文本、验证 slash / toolbar / block menu / 快捷键等编辑器行为。
- 编辑验证必须记录编辑前后截图、动作链、输入内容和清理状态。若安全清理有风险,不扩大操作,报告残留测试内容。
- 即使在 Hermes 测试页,也禁止未经确认地删除、移动、归档、发布、评论、改权限或批量修改既有非测试内容。
- 其他 Wolai 页面仍默认只读;写入仍需用户提供沙盒页 URL 和明确授权。
- 对标不能只看文案或 DOM 是否存在;必须主动检查截图中的控件形态、状态、hover/active、快捷键 toggle、关闭路径和 URL 跳转。
硬门槛:
- 不能把 subagent 文本结论当作最终对齐证据;主线程必须复核截图。
- 如果截图里有肉眼可见差异,先补 smoke 捕获差异,再改实现。
- 任何新发现的稳定失败模式,都要沉淀到 `wolai-aline` skill 的 `failure-patterns.md`
### 2.5 `基本编辑能力` 操作矩阵
`https://www.wolai.com/wolai/qN1Bh9YjLAXs8bxCJoAJ6C` 是 Wolai 对“主编辑器基础操作”的官方内容页。本页不只是说明文字,它给出了后续 `3000` 必须能复现的主编辑器操作矩阵:
| 操作 | Wolai 参考页说明 | 当前对 `Hermes` 可自动化验证性 | `3000` 后续验收要求 |
| --- | --- | --- | --- |
| 普通文本输入 / 回车 / 光标移动 | 像普通文本编辑器一样输入、换行、移动光标 | 可用 Hermes editable-test mode 取基线 | 在本地编辑态 smoke 中验证输入、换行、保存后刷新保持 |
| 块模型 | 文本、列表项、图片、文件等都是“块” | owner 态只读已可观察块 hover 手柄和 block menu | 本地每个块需有稳定 block id 与 hover target |
| 拖动块 | 块左侧 `::` 图标可拖动,并显示辅助线 | owner 态只读可观察手柄;真实拖动是写入动作,按 Hermes editable-test mode 最小验证 | 本地必须验证拖拽预览、横向 drop line、分栏 drop line |
| Esc 选中块 | 输入态按 `esc` 选中当前块,上下方向键切换,`shift + 上/下` 多选,`enter` 回编辑 | 可用 Hermes editable-test mode 取基线 | 本地必须有 block selection state、keyboard smoke |
| `cmd/ctrl + A` | 输入态第一次选中当前块文字,第二次选中所有块;块选中态直接选中所有块 | 可用 Hermes editable-test mode 取基线 | 本地必须区分 text selection 与 block selection |
| 上 / 下插入块 | hover 左侧 `::` 上方 / 下方点击 `+`;或 `esc` 选中块后按 `a` / `b` | owner 态可观察手柄;真实插入可用 Hermes editable-test mode 做最小验证 | Hermes editable-test mode 必须验证 before / after 插入与 `Esc, a` / `Esc, b` |
| 块布局显示 | `cmd/ctrl + shift + U` 显示 / 隐藏块布局虚线框 | 可用 Hermes editable-test mode 取基线 | 本地需有布局 overlay smoke,至少验证虚线框开关 |
| 分栏 | 拖块到另一个块左 / 右,辅助线由横线变竖线,松开形成分栏 | 可用 Hermes editable-test mode 取拖拽基线 | 本地可先做 drop preview,不要求完整分栏持久化一次到位 |
| 转换块 | 通过块菜单 `转换为`、快捷键、或 slash 命令转换块类型 | owner 态已可打开块菜单并看到 `转换为`;执行转换可用 Hermes editable-test mode 做最小验证 | 本地必须验证块菜单中的 `转换为` 入口与至少 paragraph/headings/list 的转换 |
| 复制 / 粘贴 | 文本或块选中后 `cmd/ctrl + C/V`,匹配样式为 `cmd/ctrl + shift + V` | 可用 Hermes editable-test mode 取基线 | 本地需验证纯文本粘贴与块复制的最小路径 |
| 缩进 / 取消缩进 | `tab` 缩进成为上一块子元素,`shift + tab` 取消缩进 | 可用 Hermes editable-test mode 取基线 | 本地需验证 list / paragraph 缩进语义与 tree projection 不冲突 |
| 文本样式工具条 | 选中文字出现工具条,支持粗体、斜体、下划线、删除线、行内代码、颜色、链接、页面引用、转换块类型 | 可用 Hermes editable-test mode 取基线 | 本地需验证 selection toolbar 出现、按钮视觉与 command dispatch |
| slash 菜单 | 输入 `/` 唤起快捷命令菜单;空行左侧 `+` 也可创建块 | 可用 Hermes editable-test mode 取基线 | 本地需验证 `/` 菜单、搜索过滤、选择条目插入块 |
本轮已能在目标页 `Hermes` 自动化验证的 published 操作:
- 顶栏搜索:打开搜索 modal,输入 `技能`,出现 `共1条匹配结果`
- 开始编辑:点击后出现 `登录以编辑` 对话框,文案为“登录后,您才可以编辑该页面,是否继续?”,包含 `取消``继续`
- 评论:点击后打开右侧评论面板,tab 为 `未解决(0)`,空态为 `还没有评论`
- 演示模式:点击后进入 presentation view,隐藏侧栏 / 顶栏,画面只保留大字号内容和演示控件。
- Good Night:右下角主题按钮切换暗色 token。
- 正文块 hover:owner 态可在块左侧看到手柄,并可点击 `.hover-block-menu` 打开块菜单;上 / 下插入块等写入路径按 `wolai-aline` 的 Hermes editable-test mode 验收。
因此,后续 `3000` 的主编辑器验收不能只做“截图像”。每个主编辑器改动都必须绑定一个可执行操作:
- 如果是 published 态能力,必须先在 Wolai published 目标页执行同类操作,再在 `3000` 执行同类操作。
- 如果是 owner/edit 态能力,必须先通过 `wolai-aline` 流程在 Wolai 目标页执行同类操作,再在 `3000` 执行同类操作。
- 如果是 owner/edit 写入能力,当前 Hermes 测试页允许最小编辑验证;其他 Wolai 页面写入必须先取得沙盒页 URL 与明确授权。
- 如果动作是在当前 Hermes 测试页做最小编辑验证,例如输入测试文本、创建临时测试块、验证 slash / toolbar / block menu / 快捷键,可按 `Hermes editable-test mode` 执行并记录前后证据;删除、移动、归档、发布、评论、改权限或批量修改既有非测试内容仍必须在动作发生前单独确认。
---
## 3. 当前代码落点
### 3.1 工作区壳与页面树
相关入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/layout.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/app-layout-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/server/sidebar-data.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/hooks/use-sidebar-data.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/tree-stream/use-sidebar-tree-stream.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-surface.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-host.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-dom-host.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/tree-projection.ts`
判断:
- `Sidebar` 外壳和 `tree-shell-*` 是页面树像素复刻的主战场。
- `tree-shell-dom-host` 承担当前树行渲染与本地展开/选中/拖拽反馈,不应再回到旧 React 树渲染器上做长期改造。
- `tree-projection` 是页面树 UI 的直接 projection 输入,不应在 UI 层重新推导排序、层级或合法性。
### 3.2 文档页与主编辑器
相关入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/(app)/documents/[id]/page.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-aggregate-loader.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-aggregate-builder.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-read-view.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-option-semantics.ts`
判断:
- `document-content.tsx` 决定 Wolai 页面顶部留白、标题、正文容器、页面元信息、右侧 inspector 和 fallback banner。
- `document-read-view.tsx` 决定阅读态块样式、任务块、标题层级、附件 / 引用 / 子页面等展示。
- `leptos-tiptap-island-editor-host.tsx` 决定编辑态输入 surface、保存、slash、工具条、块菜单和 editor runtime page options 的接缝。
- `page-option-semantics.ts` 是页面设置能否真实进入主编辑器运行时的判断边界,不能只改设置面板外观。
### 3.3 全局视觉 token
相关入口:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/globals.css`
判断:
- 像素复刻必须先固定字体、字号、行高、颜色、圆角、hover/active token。
- 当前全局字体口径有冲突:前面偏 Wolai 系统字体,后面 `html` 又偏 `Inter`。若不先收口字体栈,后续所有标题宽度、行高、树行密度都会偏。
---
## 4. Wolai 视觉目标合同
### 4.1 字体与基础排版
目标字体栈:
- 优先中文系统字体:`PingFang SC``Noto Sans CJK SC``Microsoft YaHei UI`
- 英文字体跟随系统 sans-serif,不单独强行使用 `Inter` 作为全局主字体。
目标字号与行高:
| 区域 | 字号 | 行高 | 字重 |
| --- | --- | --- | --- |
| 页面主标题 | 34px | 44px | 600 |
| 首页 / 帮助页大分组标题 | 28px | 36px | 600 |
| 次级分组标题 | 18px | 26px | 600 |
| 正文基础文字 | 16px | 24px | 400 |
| 左侧树条目 | 14px | 24px | 400 |
| 顶栏文字按钮 | 14px | 21px | 400 |
| 搜索框文字 | 14px | 22px | 400 |
目标颜色:
| 语义 | 颜色 |
| --- | --- |
| 主标题 | `rgb(30, 30, 30)` |
| 正文 | `rgba(0, 0, 0, 0.85)` |
| 次级文字 | `rgba(0, 0, 0, 0.65)` |
| 弱提示 | `rgba(0, 0, 0, 0.35)` |
| 左侧背景 | `rgb(245, 245, 245)` |
| 选中强调文字 | `rgb(207, 86, 89)` |
| 选中强调背景 | `rgba(207, 86, 89, 0.15)` |
| 顶栏 hover 背景 | `rgb(245, 245, 245)` |
### 4.2 左侧页面树
Wolai 目标特征:
- 左侧栏宽度约 `248px - 260px`,浅灰底。
- 顶部空间名行高度约 `40px`,头像为 28px 左右方形圆角块。
- 顶部快捷图标横排,图标弱色,hover 只加浅灰底。
- 搜索框尺寸约 `236px x 32px`,圆角 `4px`,内边距约 `5px 32px 5px 12px`
- 树行高度约 `32px`,内容行内 icon / arrow / title 对齐。
- 当前项使用红字 + 淡红底,背景覆盖整行可点击区域。
- 未选中 hover 使用同一红色体系,但透明度更轻。
- 缩进按层级稳定递增,不靠文本空格。
- 滚动条细、贴右侧、弱灰色,不抢视觉。
- 底部垃圾桶 / 模板中心固定在侧栏底部,边界线轻。
当前 mnote 允许保留:
- `我的页面 / Explorer` 双模式。
- 文件树作为创新入口。
- AI / Graph / 文件等自有入口。
但这些创新必须满足:
- 视觉密度服从 Wolai 左侧栏。
- 选中态和 hover 态与页面树统一。
- 不因为多了文件树而破坏 Wolai 的 32px 行节奏。
### 4.3 顶部导航与页面操作区
Wolai 目标特征:
- 顶栏高度约 `40px`
- 左侧是菜单按钮 + breadcrumb。
- breadcrumb 使用图标 / 文本 / 分隔符,字号 `14px`,颜色弱。
- 右侧按钮是弱文本 / 图标按钮,padding 约 `2px 7px`,圆角 `3px`
- owner 态可见:公开状态、收藏、演示、评论、关系、邀请、历史、更多。
- published 态可见:演示模式、搜索、开始编辑、评论、复制、开始使用 wolai。
- 顶栏 hover 不用重色按钮,只加浅灰底。
mnote 当前差距:
- 顶栏右侧已经有 Public、收藏、历史、AI、搜索、更多,但与 Wolai owner 态的 icon 序列和视觉轻重不同。
- published 视角与 owner 视角没有明确组件分型。
- breadcrumb 与页面内容标题之间的垂直节奏还需要更接近 Wolai。
### 4.4 主文档画布
Wolai 目标特征:
- 主内容列在大屏中不贴左,普通文档正文列宽约 `760px`
- 登录态 `Hermes` 页面首屏标题大约从 y=118px 开始。
- 标题为 `34px / 44px / 600`,颜色接近 `rgb(30,30,30)`
- 标题下方正文块紧跟,不默认显示额外元信息行和横向分割线。
- 普通正文块使用 `16px / 24px`
- 任务块 checkbox 轻边框,小尺寸,和文本基线对齐。
- 空白区域非常克制,不用额外卡片、阴影或说明文案。
mnote 当前差距:
- 当前本地文档页标题偏靠左且偏低,内容列与真实 Wolai 相比没有稳定对齐。
- 当前显示“个人空间 / 工作区首页”元信息和横向分割线,这不是 Wolai 普通文档页默认观感。
- 当前正文首块是“打开当前页面”链接,和目标 `Hermes` 的普通块 / 任务块 / 段落展示差异较大。
- 右下角绿色 AI 浮动按钮比 Wolai 更重,真实 Wolai 是更轻的 AI 方形入口和问号入口。
### 4.5 主编辑器块级体验
Wolai / Notion-like 目标:
- slash 是主编辑入口之一,菜单跟随 caret。
- 选中文本才出现浮动工具条。
- hover 块时只露出左侧手柄,不自动弹菜单。
- 点击左侧手柄才出现块菜单。
- 块菜单核心动作是 `turn into / duplicate / delete / drag`
- `turn into` 同时存在于文本工具条语境和块菜单语境,但命令上下文不同。
- 块 identity 由 Rust `EditorBlock.block_id` 持有;Tiptap `UniqueID` 只作为浏览器 runtime 辅助。
- 页面引用、块引用、嵌入默认位置最终必须回到 Page Aggregate / Rust artifact 边界。
#### 4.5.1 hover 上 / 下插入块
用户补充的登录态截图显示,Wolai 的块 hover 体验还有一个非常关键的细节:当鼠标停在块左侧手柄区域时,当前块行会出现很轻的淡红背景,手柄上方和下方分别出现一条短横线;继续 hover 短横线时,短横线变成 `+`,并显示黑色 tooltip
- `在上方插入块`,快捷键提示为 `Esc, a`
- `在下方插入块`,快捷键提示为 `Esc, b`
这个交互不是装饰性按钮,而是 Wolai 块编辑器的“块级插入光标”。它要和块选中、拖拽手柄、块菜单、slash 菜单、快捷键一起设计,不能只在正文左侧放一个常驻加号。
当前仓库已有可复用参考:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/blocknote-editor.tsx` 中,旧 `BlockNoteView` 通过 `SideMenuController` 注入自定义 `CustomSideMenu`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/menus/CustomSideMenu.tsx` 中,`WolaiDragHandleWithInsert` 已实现上方插入、下方插入、手柄菜单、菜单冻结、空段落加号打开 slash、截图失焦状态重置。
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-inline-editor-host.tsx` 当前已有较简化的块手柄和菜单,但没有复刻上 / 下短横线插入态。
- `/mnt/Data1T/mnote/rust/spikes/leptos-tiptap-spike/src/lib.rs` 已有 leptos/tiptap 手柄雏形,但当前样式偏重,并且 `insert_top_level_paragraph_after_html` 只支持 after 插入。
旧 BlockNote 实现中最值得迁移的机制:
- 插入控件不是放在块容器上下边界,而是围绕手柄中心点计算位置:上方插入、手柄、下方插入三者垂直对齐。
- 非 hover 状态显示 `12px` 左右短横线,hover 状态切换为 `Plus` 图标。
- 插入按钮尺寸约 `16px`,手柄按钮约 `22px`,按钮之间保持小间距,避免插入按钮和拖拽手柄互相遮挡 pointer event。
- `hoverArea` 要比按钮视觉范围更大,避免鼠标从手柄移到插入按钮时控件闪烁。
- 手柄菜单打开时需要冻结 hover 状态,否则菜单会因鼠标离开块行而消失。
- 空段落的中心 `+` 点击应等同打开 slash 菜单,而不是直接创建第二个空段落。
- 截图、窗口失焦、页面隐藏时要重置 hover / frozen 状态,避免手柄卡住或消失。
- 思维导图、在线表格、附件这类高块或内部接管鼠标事件的块,需要允许常驻或更稳定的插入控件显示策略。
迁移到 `leptos-tiptap` 时,目标行为如下:
- hover 普通块:左侧只显示三段控件,上短横线 / 拖拽手柄 / 下短横线;不自动打开块菜单。
- hover 上短横线:短横线变 `+`tooltip 显示 `在上方插入块``Esc, a`
- hover 下短横线:短横线变 `+`tooltip 显示 `在下方插入块``Esc, b`
- 点击上方 `+`:在当前块前插入空段落,焦点进入新段落。
- 点击下方 `+`:在当前块后插入空段落,焦点进入新段落。
-`Esc` 进入块选择 / 块聚焦状态后,按 `a` 执行 before 插入,按 `b` 执行 after 插入。
- 点击拖拽手柄才打开块菜单;拖拽手柄本身不和上 / 下插入按钮抢 hover。
实现边界:
- `leptos-tiptap` 需要补齐 before / after 两个编辑器命令;不能继续只有 after HTML 插入。
- 第一阶段可以先在 runtime 内插入空段落并保存正文,但正式命令口径应回到 Page Aggregate / Rust editor block model,例如 `page.body.insertBlockBefore` / `page.body.insertBlockAfter` 或等价 `EditorCommand::InsertBlock { position: Before | After }`
- UI 层可以持有当前 hover block、active block、tooltip、menu open 这些临时状态,但不能把插入后的块 id 只存在前端临时结构里。
- 视觉上应优先复刻 Wolai:轻灰短横线、浅灰 hover 背景、黑色小 tooltip、淡红块 hover 底色;不要沿用 spike 中 32px、大圆角、重阴影、绿色 accent 的手柄样式。
首轮不追求:
- 完整协作光标。
- 完整评论系统。
- 完整表格编辑体验。
- 完整图片 / 附件上传链。
- 完整权限和发布链路。
- 完整全局搜索索引。
### 4.6 操作态菜单与浮层
真实 Wolai 的体验不能只看静态首屏,必须把操作后出现的浮层也纳入复刻合同。
#### 4.6.1 页面树 owner 态菜单
用户提供的登录态截图显示,页面树当前页 `Hermes` 的管理菜单为白色浮层,宽约 220px,圆角和阴影都很轻,菜单项按语义分组:
- 打开位置:`在右侧边栏打开`,带快捷键提示。
- 页面组织:`移动到...``嵌入到...`
- 引用复制:`复制访问链接``复制页面引用链接``复制页面ID`
- 页面副本:`拷贝副本`
- 危险或编辑动作:`重命名``删除`
这说明页面树行不只是导航节点,还承载页面操作入口。mnote 复刻时必须把这些动作映射到正式命令:
- `在右侧边栏打开`UI state + route / side panel state。
- `移动到...``tree.subtree.move`
- `嵌入到...``tree.node.embed` 或 Page Aggregate embed plan。
- `复制页面引用链接`:生成 page reference artifact,不应只复制 URL。
- `重命名``page.head.updateTitle`,并同步 tree projection。
- `删除`:必须走归档 / 删除确认链,不能前端直接移除节点。
首轮可以先复刻菜单壳、分组、hover、禁用态和快捷键提示;真实移动、嵌入、删除等高风险动作按后续命令合同逐步接入。
#### 4.6.2 搜索弹层
公开页搜索和帮助中心搜索都使用居中 modal overlay
- 背景整体变暗,侧栏和正文保持原位但失焦。
- 搜索框宽约 680px,高约 56px,白底,圆角轻,阴影明显但不厚重。
- 输入后出现选项行:`仅匹配标题``精确匹配``页面内搜索`
- 结果区显示匹配数量和快捷键说明:`Ctrl + Enter 新窗口打开 / Alt + Enter 右侧边栏打开`
- 结果行左侧是页面 / 块图标,中间是标题与命中摘要,命中词使用红色高亮,右侧显示所在页面或空间。
mnote 需要把搜索拆成两层:
- 左侧栏 `快速筛选页面`:只过滤当前页面树,结果直接替换树列表。
- 顶栏搜索 modal:跨页面 / 页面内搜索入口,支持结果列表、快捷键提示、打开方式提示。
首轮可以用本地 projection / 当前页面块快照生成结果,不必先接全局索引;但视觉结构、选项行和打开方式提示应先复刻。
#### 4.6.3 published 态操作
真实 published 态存在几类弱操作入口:
- `演示模式`:点击后隐藏侧栏和顶栏,画布变成大字号演示页,只保留主题切换入口。
- `开始编辑`:未登录时弹出“登录以编辑”确认框,红色主按钮为继续,取消为弱按钮。
- `评论`:右侧滑出评论面板,顶部有 `未解决(0)` tab、筛选下拉、关闭按钮,空态居中。
- `Good Night`:右下角主题按钮直接切换暗色主题,暗色主题不重排页面,只替换 token。
mnote 首轮不必做完整发布权限,但应该保留这套状态分型:
- owner 编辑态。
- published 阅读态。
- presentation 演示态。
- comment side panel。
- light / dark token 切换。
这些状态大多是 UI state,但 `开始编辑` 是否可用、评论权限、发布状态必须来自页面权限或发布 projection,不应在前端硬猜。
### 4.7 帮助中心页的内在逻辑
`https://www.wolai.com/wolai/xhqeop8UHpVTMUSVmgz8nq` 不是普通内容页,它展示了 Wolai 页面系统的内在组织方式。
观察结论:
- 左侧页面树是帮助主题的完整目录,根页面下挂大量子页面,既是导航也是信息架构。
- 正文入口页不是自由排版海报,而是由页面链接、分组标题、四列目录块、子页面引用组成的“文档门户”。
- `快速上手 / 常见问题 / 视频教程 / 使用技巧短视频` 是高频入口。
- “让我们先掌握一些基础”下的四列目录把页面按能力分组:基础操作、基础块类型、进阶块类型、媒体与文件。
- 目录项本质上是页面引用 / 页面链接,不是普通文本。
- 搜索可以跨整个帮助中心返回页面和块级结果,命中词红色高亮。
- 搜索结果可以按快捷键在新窗口或右侧边栏打开,说明“右侧边栏打开”是 Wolai 页面导航模型的一等入口。
更重要的是,帮助中心中的“块引用”页直接定义了编辑器的语义模型:
- 行内块引用:在文字中间引用另一个块,底部有圆点虚线,不能直接编辑,点击跳转原始出处。
- 嵌入块引用:以独立块引用另一个块,左侧有圆点虚线,可以直接查看或部分编辑,但不能删除被引用块本身。
- 页面引用:当对象是页面时,菜单从“复制块引用链接”变为“复制页面引用链接”。
- 行内页面引用:在文本内显示页面标题。
- 嵌入页面引用:独立引用块显示页面图标和标题。
- 复制粘贴路径:块菜单复制引用链接,再粘贴生成引用。
- 快捷键路径:`esc` 选中块,`H` 复制行内块引用链接,`Q` 复制嵌入块引用链接。
- 嵌入到路径:`cmd/alt + shift + G` 打开“嵌入到”弹窗,搜索目标页面,把块嵌入到目标页面。
- 当前页面位置快速引用:编辑时输入 `[[` 搜索页面,添加页面行内引用。
- 右侧边栏拖拽引用:从右侧边栏拖拽产生引用。
- 引用预览和别名:悬浮行内块引用出现预览,小窗顶部可以设置别名,别名末尾显示箭头。
因此,mnote 不应把“引用”当成一个简单链接样式。它至少需要在 Page Aggregate / editor block model 中区分:
- `inline_page_reference`
- `inline_block_reference`
- `embedded_page_reference`
- `embedded_block_reference`
- `reference_alias`
- `reference_preview`
- `reference_source_block_id`
- `reference_target_page_id`
首轮实现可以降级,但语义模型不能降级成普通 URL。
---
## 5. 复刻范围分级
### 5.1 P0:先做像素基线
P0 只做最影响首屏观感的部分:
- 全局字体栈与基础字号行高收口。
- Sidebar 宽度、背景、顶部栏、树行高度、选中态、hover 态。
- 顶栏高度、breadcrumb、右侧动作按钮轻量化。
- 文档标题位置、字号、行高、正文列宽。
- 默认隐藏普通文档页中的额外元信息行和分割线。
- 右下角浮动入口减重。
-`1392 x 1213` 视口下对齐真实 Wolai 截图。
### 5.2 P1:补齐高频交互体验
P1 做用户会立刻感知的交互:
- 页面树展开 / 折叠动画和 hover action。
- 当前页祖先链展开和选中态保持。
- 搜索框视觉与本地过滤体验。
- 树行上下文菜单的视觉壳和核心动作入口。
- 顶栏搜索 modal 的选项行、结果行、命中高亮和快捷键提示。
- 评论侧栏、登录编辑确认框、演示模式的基础状态切换。
- 文档页进入编辑后,slash、浮动工具条、左侧手柄、上 / 下插入块、块菜单的基础可用体验。
- 页面标题编辑后,标题 / breadcrumb / sidebar / page tree / file tree 保持一致。
### 5.3 P2:接近 Wolai 的产品完整感
P2 做复杂但可逐步推进的部分:
- 拖拽排序与 drop feedback。
- page reference / block reference 的正式插入体验。
- `[[` 页面引用搜索、`嵌入到...` 弹窗、右侧边栏打开 / 拖拽引用。
- 引用预览和别名。
- 右侧评论、历史、关系图入口的真实面板。
- published / owner / edit 三态顶栏完整切换。
- 页面设置项与 editor runtime 的完整联动。
- 帮助中心类多列目录块的静态展示和后续编辑支持。
### 5.4 明确降级项
以下功能可以先只做视觉入口或最小实现:
- 评论协作。
- 权限分享。
- 发布与侵权投诉链路。
- 全局搜索索引。
- 完整演示模式。
- 完整夜间主题。
- 完整表格、图表、数据库。
- 完整文件上传和媒体块管理。
- 完整引用别名编辑器和跨页面引用预览缓存。
---
## 6. 单一真源边界
### 6.1 可以留在前端展示层的内容
以下内容允许作为前端展示或临时 UI state:
- 色彩、字号、行高、间距、圆角、阴影。
- Sidebar 宽度与布局。
- 树行 hover、focus、临时 selection、drag preview。
- 顶栏按钮排列与 hover。
- 文档画布宽度、标题留白、正文显示密度。
- slash 菜单开合状态。
- 浮动工具条开合状态。
- 块手柄 hover 状态。
- 右下角浮动入口显示状态。
### 6.2 必须回到 Rust / projection / Page Aggregate 的内容
以下内容不能只在前端复刻:
- 页面树真实层级。
- 排序真相。
- 当前页 active path。
- 拖拽是否合法。
- 新建、重命名、移动、归档、恢复结果。
- 页面标题与 breadcrumb / sidebar / file tree 的一致性。
- 正文保存与 conflict key。
- 页面设置对 editor runtime 的正式语义。
- page reference / block reference / embed 默认位置。
- 引用类型、引用源、引用目标、引用别名、引用预览数据。
- AI 写入标题、正文、页面设置的语义边界。
原则:
> **UI 可以复刻 Wolai,事实源不能复刻成第二份前端状态。**
### 6.3 命令口径
页面树动作继续沿 `tree.*`
- `tree.node.create`
- `tree.node.rename`
- `tree.subtree.move`
- `tree.node.archive`
- `tree.node.restore`
- `tree.node.embed`
页面动作继续沿 `page.*`
- `page.head.updateTitle`
- `page.layout.updateOptions`
- `page.body.save`
兼容 `documents.*` 可以继续存在,但不应作为新增体验的正式命令面。
---
## 7. 推荐实施路线
### Phase A:建立 Wolai 视觉 token
目标:
> **先让字体、字号、行高、颜色和基础密度可被统一复用。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/app/globals.css`
工作内容:
- 统一全局字体栈,避免 `Inter` 覆盖中文系统字体。
- 建立 Wolai-like token`--wolai-sidebar-bg``--wolai-active-fg``--wolai-active-bg``--wolai-text-primary``--wolai-text-secondary`
- 固定标题、正文、sidebar、topbar 的字号 / 行高。
- 用 token 替代散落硬编码颜色。
验收:
- `local-3000` 截图中标题、树行和顶栏文字宽度明显接近真实 Wolai。
- 中文字体不再出现 Inter 优先导致的字宽偏差。
### Phase B:复刻 Sidebar / 页面树首屏
目标:
> **让左侧页面树在密度、选中态、hover、滚动条、底部入口上接近 Wolai。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/sidebar.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-surface.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-host.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/sidebar/tree-shell-dom-host.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/tree-projection.ts`
工作内容:
- 固定侧栏宽度和背景。
- 调整 workspace header 高度、头像尺寸、空间名截断方式。
- 调整快捷图标行尺寸与 hover。
- 调整页面树行高到 32px 附近。
- 当前页改为 Wolai 红字 + 淡红底。
- hover 态与 active 态同色系但层级更轻。
- 缩进只由层级和 icon slot 决定。
- 底部垃圾桶 / 模板中心与 Wolai 对齐,同时保留 mnote 自有入口时不破坏密度。
- `Explorer` 作为 mnote 创新保留,但视觉上降噪,避免抢过“我的页面”主路径。
验收:
- 对照用户提供的登录态截图,`Hermes` 选中行的颜色、行高、左侧缩进、滚动条位置接近 Wolai。
- 页面树长列表滚动时不出现行高抖动。
- 文件树创新入口仍可访问。
### Phase C:复刻顶栏与 breadcrumb
目标:
> **让顶栏在 owner / published 两种状态下都接近 Wolai 的轻量工具条。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/app-layout-shell.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
工作内容:
- 固定顶栏高度约 40px。
- breadcrumb 文本、图标、分隔符弱化。
- 右侧按钮统一使用轻量 icon/text button。
- 区分 owner 态和 published 态的按钮组合。
- 公开状态 pill 对齐 Wolai 的“全网公开 / Public”视觉。
- 顶栏 hover 只加浅灰底,不使用重边框或大面积按钮。
验收:
- 登录态参考图中的顶栏图标序列能在 mnote 中找到对应视觉位置。
- published 公共页参考图中的“演示模式 / 搜索 / 开始编辑 / 评论 / 复制 / 开始使用 wolai”可先作为视觉分型,不要求一次接齐功能。
### Phase D:复刻文档画布与阅读态
目标:
> **让普通文档页首屏像 Wolai,而不是像调试页或二级详情页。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-content.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/document-read-view.tsx`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-option-semantics.ts`
工作内容:
- 标题使用 `34px / 44px / 600`
- 普通正文列宽约 760px。
- 调整首屏 top padding,使标题起点接近 Wolai。
- 默认隐藏普通文档页的“个人空间 / 工作区首页”元信息行和横向分割线。
- 保留页面设置控制,但把“显示元信息 / 显示结构”这类调试或增强项作为显式选项,不默认露出。
- 阅读态任务块、段落、标题、列表、引用、代码块按 Wolai 行高和颜色重调。
- 右下角 AI / 帮助入口减重,避免绿色大按钮抢主编辑区视觉。
验收:
- `Hermes` 类普通文档页首屏只突出标题和正文块。
- 页面不再默认出现明显不像 Wolai 的横线、meta row 或 debug outline。
- 阅读态和编辑态在基础排版上不出现明显跳动。
### Phase E:复刻主编辑器高频块交互
目标:
> **让 `leptos-tiptap` 的输入体验从“能编辑”推进到“像 Wolai 的块编辑器”。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/components/editor/leptos-tiptap-island-editor-host.tsx`
- `/mnt/Data1T/mnote/rust/crates/core-protocol/src/editor/model.rs`
- `/mnt/Data1T/mnote/rust/crates/bridge-runtime/`
- `/mnt/Data1T/mnote/design/05-editor-mainline/reference-code/tiptap-notion-like-registry/`
工作内容:
- slash 菜单锚定 caret。
- selection 存在时显示浮动工具条。
- hover 块只显示左侧手柄区,上方和下方短横线在 hover 时变成插入 `+`
- 点击上方 / 下方插入控件分别创建 before / after 空段落,并把焦点移动到新段落。
- 支持 `Esc, a``Esc, b` 的块级插入快捷键。
- 点击手柄打开块菜单。
- 块菜单至少提供 `turn into / duplicate / delete` 的视觉与基础命令入口。
- `turn into` 不直接绕过 Rust block model。
- Tiptap `UniqueID` 只用于 runtime 辅助,最终块 id 对齐 Rust `EditorBlock.block_id`
- 页面引用 / 块引用可先做最小插入体验,复杂搜索和预览后置。
验收:
- 用户能在主编辑区通过 slash 插入常见块。
- 用户选中文本时看到 Wolai-like 浮动工具条。
- 用户 hover 块时不会被重工具条打扰,并能看到 Wolai-like 上 / 下插入短横线。
- 用户可以通过鼠标或 `Esc, a` / `Esc, b` 在当前块前后插入空段落。
- 块操作不会制造前端临时 id 作为持久化真相。
### Phase F:用 Page Aggregate 守住一致性
目标:
> **复刻体验时不牺牲单一真源。**
主要文件:
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-aggregate-loader.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-aggregate-builder.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/lib/documents/page-command-client.ts`
- `/mnt/Data1T/mnote/wolai-frontend/src/app/api/documents/page/route.ts`
- `/mnt/Data1T/mnote/rust/crates/core-protocol/`
- `/mnt/Data1T/mnote/rust/crates/bridge-runtime/`
- `/mnt/Data1T/mnote/rust/crates/mnote-web/`
工作内容:
- 新增视觉状态时,先判断它是否只是 UI state。
- 新增页面字段时,必须归入 `page_identity / page_head / page_layout / page_body / page_tree`
- 标题变更必须继续让页头、breadcrumb、sidebar、page tree、file tree 同步。
- 正文保存继续走 `page.body.save`
- 页面设置继续按 `page-option-semantics` 判断是否进入 island runtime。
- 页面树动作继续走 `tree.*`,不新增 `documents.*` 长期动作。
验收:
- 页面重命名后,所有消费者保持一致。
- 切页、刷新、实时更新不会把 UI 压回旧标题。
- 视觉复刻代码里没有新增第二套树排序或页面标题真相。
### Phase G:截图回归与像素验收
目标:
> **把“像 Wolai”变成可复查、可操作、可重复的截图基线。**
建议新增或复用:
- `/mnt/Data1T/mnote/scripts/task*-smoke.js`
- `/mnt/Data1T/mnote/tmp/wolai-compare/`
验收视口:
- 桌面:`1392 x 1213`
- 宽屏:`1440 x 900`
- 窄屏:`390 x 844`
截图基线:
- 真实 Wolai 登录态长页面树。
- 真实 Wolai `Hermes` 普通文档页。
- 当前 mnote 同类页面。
- mnote 文件树创新入口展开态。
验收方式:
- 每次验收必须按 `wolai-aline` skill 形成差异矩阵,并附 Wolai 截图、本地截图、动作链、DOM/ARIA 线索和剩余差异。
- 浏览器取证必须由 subagent 执行;主线程必须复核截图,不能只接受 subagent 文本结论。
- published 阅读态、owner 登录态、editable-test mode 都只表示取证上下文,不再作为三套独立测试方案维护。
- 先用 Wolai 基线写出本地 RED smoke,再实现,再跑本地 smoke,再派 subagent 复测。
- 若截图肉眼可见差异但 smoke 未捕获,先增强 smoke 或 checklist 断言,再继续实现。
- 后续可加入截图 diff,但不能用截图 diff 替代真实交互烟测。
- 每次改 Sidebar、topbar、document canvas、editor runtime 后都要重新截首屏,并同步更新连续 checklist 的状态。
- 连续执行清单见 `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-9-wolai-aline-continuous-checklist-v1.md`
owner 态交互验收清单:
- 自动化打开 `https://www.wolai.com/liaibo/ikFSSM1a4GgvmHBYFCNfVd` 后确认进入登录 owner 态,而不是 published 态。
- hover 页面树当前项 `Hermes`,确认 hover action 可见。
- 打开页面树菜单,截图菜单项和分组。
- hover 正文块 `事实上`,确认块背景、左侧手柄、上短横线、下短横线可见。
- hover 上短横线,确认 tooltip 为 `在上方插入块` / `Esc, a`
- hover 下短横线,确认 tooltip 为 `在下方插入块` / `Esc, b`
- 打开块菜单,截图 `AI 助理 / 转换为 / 拷贝副本 / 删除 / 复制链接 / 移动/嵌入到... / 块历史... / 评论 / 颜色 / 文字居中 / 文字翻译 / 生成海报` 等入口。
-`3000` 执行同类操作并保存同视口截图。
- 验收报告中列出 Wolai 截图、本地截图、差异结论和不可测项。
---
## 8. 验证命令建议
前端运行:
```bash
cd /mnt/Data1T/mnote/wolai-frontend && pnpm dev
```
前端相关测试:
```bash
cd /mnt/Data1T/mnote/wolai-frontend && pnpm test src/components/sidebar/tree-shell-host.test.tsx src/components/sidebar/tree-shell-surface.test.tsx src/components/editor/document-content.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx
```
projection / aggregate 测试:
```bash
cd /mnt/Data1T/mnote/wolai-frontend && pnpm test src/lib/tree-projection.test.ts src/lib/tree-projection-contract.test.ts src/lib/documents/page-aggregate-builder.test.ts src/lib/documents/tree-command-client.test.ts
```
Rust 侧测试:
```bash
cd /mnt/Data1T/mnote && cargo test -p mnote-web workspace_shell
cd /mnt/Data1T/mnote && cargo test -p bridge-runtime
```
浏览器验收:
- 先按 `wolai-aline` skill 派 subagent 打开真实 Wolai 参考页和 `http://127.0.0.1:3000/`
- 统一视口至少覆盖 `1392 x 1213`;涉及响应式时补 `1440 x 900``390 x 844`
- 每个小任务都要保存 Wolai 与本地截图,并记录入口、关闭路径、URL 变化、控件形态、控件状态、快捷键、hover/active、DOM/ARIA 线索。
- 主线程复核截图后,把结论写入差异矩阵;发现差异先补 smoke,再改实现。
- 对照范围至少包含左侧栏宽度、树行高度、选中态、正文标题位置、顶栏按钮密度、搜索 modal、块 hover/插入入口、右下角浮动入口。
---
## 9. 禁止事项
为避免这条线跑偏,后续实现中禁止:
- 不把 `BlockNote` 写回默认主编辑器。
- 不把 Next App Router 恢复成 `3000` 主入口。
- 不在 UI 层重新拼第二份页面树、排序、标题或正文真相。
- 不把 compat route 或 debug shell 当正式主链。
- 不为了像素复刻绕开 `tree.*` / `page.*` 命令族。
- 不把 Tiptap runtime 临时 id 倒灌成 Rust 持久化块 id。
- 不把页面设置做成“保存了字段但 editor runtime 不消费”的假功能。
- 不为了复刻 Wolai 顶栏而提前接入真实权限 / 分享 / 评论复杂链路。
---
## 10. 当前退出标准
这份方案只有在下面条件同时满足后,才能移入 `done`
- `3000` 首屏在左侧页面树、顶栏、文档画布三块视觉上接近真实 Wolai。
- 页面树保留 mnote 文件树创新,但不破坏 Wolai 主路径密度。
- 普通文档页默认不再显得像调试页或二级详情页。
- `leptos-tiptap` 主编辑器具备 slash、浮动工具条、左侧手柄、上 / 下插入块、块菜单的基础 Wolai-like 行为。
- 标题 / 正文 / 页面设置 / 页面树仍服从 Page Aggregate 与 tree-first graph kernel 主线。
- 相关 smoke / 单测 / 截图验收有记录。
- 连续 checklist 中 P0/P1 任务均有 Wolai 基线、本地 RED/GREEN smoke、subagent 复测截图和主线程复核结论。
当前结论:
> **下一步应先做 P0 像素基线和 Sidebar / 文档画布首屏对齐,再推进编辑器块级交互。功能复杂项可以降级,但页面树和主编辑器的视觉节奏必须优先向真实 Wolai 收口。**
@@ -0,0 +1,41 @@
# 5-8 [process] Wolai 编辑态自动化测试方案 v1(已收口)
> 更新时间:2026-04-30
>
> 状态:本文件原有的工具筛选、只读边界、沙盒页要求和脚本草案已经被统一的 `wolai-aline` skill 与 `08-wolai-aline-test-flow` 流程取代。后续不要再按本文旧版测试方案执行。
## 当前唯一口径
后续所有 Wolai 对标、复刻、aline/alignment、编辑器状态自动化测试任务,统一使用:
- Codex skill`/home/lix/.codex/skills/wolai-aline`
- 项目流程:`/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
- 项目规则:`/mnt/Data1T/mnote/AGENTS.md``Wolai-aline 对标流程`
## 取代的旧结论
以下旧结论不再作为执行依据:
- “Hermes 页面只能只读,写入必须另找沙盒页”。
- “published 态脚本可以代表主编辑器完成验收”。
- “subagent 文本结论或文案断言足以证明对齐”。
- “先实现再由用户肉眼指出差异”。
- 本文旧版 `wolai-reference-*` 脚本草案、分阶段 checklist、工具通道优先级。
## 现行规则摘要
- 当前 Hermes 测试页 `https://www.wolai.com/liaibo/ikFSSM1a4GgvmHBYFCNfVd` 已授权用于 Wolai-aline 编辑器对标中的最小编辑验证。
- 编辑验证必须记录前后截图、动作链、输入内容和清理状态。
- 即使在 Hermes 测试页,也禁止未经确认地删除、移动、归档、发布、评论、改权限或批量修改既有非测试内容。
- 其他 Wolai 页面仍默认只读;写入仍需用户提供沙盒页 URL 和明确授权。
- 浏览器对标测试必须使用 subagent 执行;主线程必须复核截图并形成差异矩阵。
- 发现差异后先补本地 smoke,让差异可复现失败,再实现修复。
## 后续维护
若后续 Wolai-aline 任务发现新的稳定失败模式,应更新:
- `/home/lix/.codex/skills/wolai-aline/references/failure-patterns.md`
- 必要时同步更新 `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`
不要继续扩写本文作为新测试方案。
@@ -0,0 +1,273 @@
# 5-9 [process] Wolai-aline 连续执行 checklist v1
> 更新时间:2026-04-30
>
> 本清单拆自 `5-7-wolai-page-tree-main-editor-experience-restoration-v1.md`。它不是新的测试方案;执行口径统一服从 `/home/lix/.codex/skills/wolai-aline` 与 `/mnt/Data1T/mnote/design/08-wolai-aline-test-flow/process/wolai-aline-test-flow-v1.md`。
## 1. 使用方式
每次只领取一个最小可对标任务,按下面固定闭环推进:
1. 用一句话定义行为,例如“侧栏搜索入口打开全局搜索 modal 且 URL 不变”。
2. 派 subagent 对 Wolai Hermes 取基线,必要时进入 Hermes editable-test mode。
3. 主线程查看 Wolai 截图,填写差异矩阵,不接受只有文字的结论。
4. 写或更新本地 `scripts/task*-smoke.js`,先让当前 `3000` 暴露差异。
5. 小范围实现,不新增第二套页面树、标题、正文或排序真相。
6. 运行本地 smoke 和改动范围内单测。
7. 再派 subagent 同链路复测 Wolai 与 `3000`
8. 主线程复核最终截图;若肉眼仍有差异,先补 smoke 或 checklist 断言,再继续修。
9. 更新本清单状态和截图路径。
状态标记:`TODO` 未开始,`BASELINE` 已取 Wolai 基线,`RED` 已有本地失败 smoke`GREEN` 本地实现和 smoke 通过,`PARITY` subagent 复测与主线程截图复核通过,`BLOCKED` 有阻塞。
## 2. 当前基线证据
2026-04-30 已由 subagent 使用 Playwright 同时操作 Wolai Hermes 与本地 `3000`,主线程已复核关键截图。后续任务可沿用这些基线,但每个具体小任务仍需重新截同动作链证据。
| 项 | Wolai 证据 | 本地证据 | 当前结论 |
| --- | --- | --- | --- |
| 首屏 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/doc-baseline-mini/wolai-initial.png` | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/doc-baseline-mini/local-initial.png` | 本地首屏仍是聚合/预览壳,需点击 `打开当前页面` 才进入正文,和 Wolai 直接正文页不一致 |
| 搜索 modal | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-03-search-modal-controls-filled.png` | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/local-03-search-modal-controls-filled.png` | 容器接近;结果态、数据、入口矩阵仍需拆开验;switch 状态必须截图 + `aria-checked` 双证据 |
| 块 hover | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-08-block-hover-insert-entry.png` | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/local-14-block-hover-insert-entry.png` | 本地已有 `+` 与手柄入口,但位置、密度、页面状态不同,不可判定已对齐 |
| 编辑清理 | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/wolai-14-title-suffix-after.png` | `/mnt/Data1T/mnote/tmp/wolai-editor-parity/baseline-20260430/local-16-editor-after-undo-cleanup.png` | Wolai 标题和正文都可编辑,editable-test 前必须先确认焦点 block 类型 |
当前必须优先拆解的差异:
1. 普通文档首屏入口语义:Wolai 直接显示正文页,本地仍需点击 `打开当前页面`
2. 搜索入口矩阵:Wolai 左侧搜索可打开,`Ctrl+K` 本轮未稳定;本地顶栏搜索可打开,侧栏入口和快捷键要继续按 smoke 验证。
3. 搜索结果数据:Wolai 对 `Hermes` 有真实结果,本地依赖当前工作区示例数据;后续需要固定 fixture 或种子。
4. 编辑器测试焦点:写入前必须确认当前焦点在正文测试块,不在标题或 breadcrumb 渲染副本。
### 2.1 task129 Phase A 执行记录
2026-04-30 已新增并执行 `scripts/task129-wolai-aline-baseline-smoke.js`,用于固化 Phase A 的只读基线取证入口。该脚本不编辑 Wolai,只做双端首屏取证、DOM 摘要和差异矩阵输出。
主线程验证:
- 命令:`WOLAI_ALINE_RUN_ID=main-20260430-122309 node scripts/task129-wolai-aline-baseline-smoke.js`
- 结果:通过,`ok: true`,确认 `simultaneous: true`
- 矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/main-20260430-122309/comparison-matrix.md`
- Wolai 截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/main-20260430-122309/wolai-1392x1213-first-screen.png`
- 本地截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/main-20260430-122309/local-1392x1213-first-screen.png`
主线程多视口验证:
- 命令:`WOLAI_ALINE_RUN_ID=main-viewports-20260430-122418 WOLAI_ALINE_VIEWPORTS=1392x1213,1440x900,390x844 node scripts/task129-wolai-aline-baseline-smoke.js`
- 结果:通过,生成 `1392x1213``1440x900``390x844` 三组 Wolai / 本地截图
- 矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/main-viewports-20260430-122418/comparison-matrix.md`
subagent 复测:
- 命令:`WOLAI_ALINE_RUN_ID=subagent-20260430-122359 node scripts/task129-wolai-aline-baseline-smoke.js`
- 结果:通过,`ok: true`,确认 `simultaneous: true`,本次不需要 `xvfb-run`
- 矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/subagent-20260430-122359/comparison-matrix.md`
- Wolai 截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/subagent-20260430-122359/wolai-1392x1213-first-screen.png`
- 本地截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/subagent-20260430-122359/local-1392x1213-first-screen.png`
主线程截图复核结论:
- Wolai Hermes 为 owner 登录态,直接显示正文页和 `全网公开` 状态。
- 本地 `3000` 工作区可见,但首屏仍停留在聚合 / 预览入口,需要点击 `打开当前页面` 才进入正文。
- `task129` 只证明取证工具链可用,不证明 UI 已对齐;后续首个实现任务应优先处理 `D9 普通文档首屏入口语义`
### 2.2 task130 D9 执行记录
2026-04-30 已新增并执行 `scripts/task130-wolai-aline-root-document-entry-smoke.js`,用于固化 `D9 普通文档首屏入口语义`。该脚本启动临时 `mnote-web` 端口验证新代码路径,不修改现有 `3000` 进程。
RED
- 初次运行失败:`AssertionError: 根页不应停在需要点击“打开当前页面”的聚合入口`
- 失败原因:Rust Web root route 在有 active page 时只渲染 workspace entry 和 `打开当前页面` 链接,没有内嵌 Page Aggregate、editor bootstrap 和 `leptos-tiptap` island。
GREEN
- 命令:`node scripts/task130-wolai-aline-root-document-entry-smoke.js`
- 结果:通过
- 本地截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task130-root-document-entry/local-root-document-entry.png`
- smoke 断言:根页不再出现 `打开当前页面`;存在 `document-shell[data-editor-host="leptos_tiptap_island"]`;存在 `__MNOTE_PAGE_AGGREGATE__``__MNOTE_EDITOR_BOOTSTRAP__`;存在 `.ProseMirror[contenteditable="true"]`
实现摘要:
- root route 在 active page 可加载 Page Aggregate 时,直接复用 `DocumentPage`、Page Aggregate snapshot、editor bootstrap、title controller 和 island adapter。
- 若 active page 不能加载 Page Aggregate,回退旧 workspace entry,避免 recent cookie 指向失效页面时 root 直接 400。
- 现有 `3000` 进程仍是旧实例;需要重启 `mnote-web` 后,`3000` 才会体现该改动。
验证:
- `cargo fmt -p mnote-web --check`
- `cargo check -p mnote-web`
- `cargo test -p mnote-web root_entry -- --nocapture`
- `node scripts/task130-wolai-aline-root-document-entry-smoke.js`
最终对标复核:
- subagent 命令:`node scripts/task130-wolai-aline-root-document-entry-smoke.js`
- 结果:通过
- Wolai 基线:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task129-baseline/main-20260430-122309/wolai-1392x1213-first-screen.png`
- 本地截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task130-root-document-entry/local-root-document-entry.png`
- 结论:D9 目标行为一致;剩余侧栏数据、图标细节、具体字号属于后续 Sidebar / visual token checklist。
## 3. 通用证据矩阵
每个 checklist 项都必须保留以下字段:
| 字段 | 要求 |
| --- | --- |
| Wolai 截图 | 绝对路径,保存在 `/mnt/Data1T/mnote/tmp/wolai-editor-parity/<task>/` |
| 本地截图 | 同视口、同动作链截图 |
| 入口 | 按钮、快捷键、菜单或 hover 区域 |
| 关闭路径 | Esc、再次快捷键、遮罩、关闭按钮、入口 toggle |
| URL | 操作前后是否变化 |
| 控件类型 | switch、button、dropdown、menu、input、panel、modal |
| 控件状态 | checked、selected、disabled、focused、hover、active |
| 视觉形态 | 尺寸、位置、间距、阴影、颜色、图标、遮罩 |
| DOM/ARIA | role、aria、data-testid、可聚焦性 |
| smoke | 对应 `scripts/task*-smoke.js` 或测试文件 |
| 剩余差异 | 不允许空泛写“基本一致” |
## 4. Phase A:基线和全局 token
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| A1 | GREEN | 固定基线视口与截图目录 | `task129` 已支持 `WOLAI_ALINE_VIEWPORTS`;主线程已生成 `1392x1213``1440x900``390x844` 三组截图;subagent 已复测默认视口 |
| A2 | PARITY | Wolai owner 首屏基线 | `task129` 主线程与 subagent 均确认 Wolai owner 登录态,截图见 2.1 执行记录 |
| A3 | PARITY | 本地 `3000` 首屏基线 | `task129` 主线程与 subagent 均确认本地 3000 可打开,截图见 2.1 执行记录 |
| A4 | TODO | 字体栈与基础 token | smoke 或 DOM 断言全局字体、标题字号、正文行高、sidebar 行高;截图确认中文字宽接近 Wolai |
| A5 | PARITY | 截图差异表模板落地 | `task129` 自动输出 `comparison-matrix.md`,主线程已复核截图,subagent 已独立复测 |
| A6 | PARITY | 同时操作工具链固化 | `task129` 固定 `Playwright + Chrome persistent profile`;主线程与 subagent 均确认可同时操作两个目标,本次 subagent 不需要 `xvfb-run` |
## 5. Phase BSidebar / 页面树
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| B1 | PARITY | 侧栏宽度与背景 | `task131` 覆盖宽 `248px`、背景 `rgb(245,245,245)`、无右侧 inset、topbar.x=sidebar.rightsubagent post-fix2 复核通过 |
| B2 | PARITY | workspace header | `task132` 覆盖 header `40px`、avatar `24x24`、字号 `16px`;Wolai DOM 锚点弱,主线程与 subagent 以截图复核通过 |
| B3 | PARITY | 快捷入口行 | `task132` 覆盖 6 个快捷入口、30px 容器、约 10.4px 间距;截图复核通过 |
| B4 | PARITY | 页面树行密度 | `task132` 覆盖行高 `32px`、字号 `16px`、无额外行 margin;截图复核通过 |
| B5 | PARITY | 当前页选中态 | `task132` 覆盖淡红底 `rgba(255,71,71,.1)`、红字、`aria-current=page` / `aria-selected=true`;截图复核通过 |
| B6 | PARITY | 层级缩进 | `task132` 使用父子页面 fixture 覆盖行 x 不变、一级子行内部缩进 `20px`;服务端和运行时均补齐 depth 语义 |
| B7 | PARITY | 页面树 hover action | `task132` 覆盖 hover 仅出现“更多操作 / 新建子页面”两个 24px actionsubagent post-fix2 复核通过 |
| B8 | PARITY | 页面树上下文菜单 | `task132` 覆盖宽约 `220px`、9 项命令、分组间距和 `Alt+` / `Del` 提示;高风险删除仍经确认 |
| B9 | PARITY | 底部垃圾桶/模板中心 | `task132` 覆盖 footer `44px`、无顶部分隔线、垃圾箱/模板中心固定底部;截图复核通过 |
| B10 | PARITY | 文件树创新入口 | 保留 Explorer tab`task132` 复核主路径仍是“我的页面”,footer 与行高体系不被破坏;与 Wolai 无 Explorer 的差异按本地创新入口保留 |
| B11 | PARITY | 页面树图标与展开箭头 | `task133` 覆盖 Wolai 每行不同私有 glyph、本地无真实 icon 数据时不得用统一房子图标,也不保留空 icon slot;展开箭头使用 20x20 SVG chevron |
### 5.1 task131 / task132 / task133 Phase B 执行记录
2026-04-30 已完成 Phase BSidebar / 页面树。执行口径按 `wolai-aline` skill:先由 subagent 取 Wolai 基线,主线程查看截图并写 RED smoke,再最小实现,最后本地与 subagent 复测。用户复核指出 `task132` 漏掉页面树图标/箭头差异后,补充 `task133` 将该类肉眼差异固化为 smoke。
RED / GREEN
- `scripts/task131-wolai-aline-sidebar-b1-smoke.js`:固化 B1,初始失败于本地侧栏 `240px`GREEN 后覆盖 `248px``rgb(245,245,245)`、无 right inset、topbar 贴合。
- `scripts/task132-wolai-aline-sidebar-phase-b-smoke.js`:固化 B2-B10,使用父子页面 fixture 覆盖 header、快捷入口、行密度、选中态、层级缩进、hover action、上下文菜单、footer。
- `scripts/task133-wolai-aline-sidebar-tree-icons-arrows-smoke.js`:固化 B11,RED 失败于页面树统一房子 glyph;二次 GREEN 后断言 page 模式不渲染通用房子/伪 icon、不保留空 icon 槽,标题贴近箭头/占位,toggle 使用 20x20 SVG chevron。
实现摘要:
- `rust/crates/mnote-web/src/ssr/styles.rs`:侧栏宽度/背景、header、quick actions、tree row、选中态、context menu、footer 的 Wolai 对齐样式;page 模式无真实 icon 数据时不显示统一房子 glyph,也不保留空 icon 槽。
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`:页面树运行时渲染补 `aria-current` / `aria-selected`hover action 收敛为“更多/新建”,上下文菜单命令与 Wolai 对齐,运行时递归 depthpage tree 展开箭头改为 20x20 SVG chevronpage 行不渲染空 icon 节点。
- `rust/crates/mnote-web/src/tree_shell/page_renderer.rs` / `routes/tree.rs`:SSR 页面树补父子层级 depth / aria-level 语义,兼容 `parentId``parentNodeId`SSR page tree 展开箭头同样使用 20x20 SVG chevronpage 行不渲染空 icon 节点。
验证命令:
- `cargo fmt -p mnote-web --check`
- `cargo check -p mnote-web`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task131-wolai-aline-sidebar-b1-smoke.js`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task132-wolai-aline-sidebar-phase-b-smoke.js`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task133-wolai-aline-sidebar-tree-icons-arrows-smoke.js`
- `node scripts/task119-rust-web-wolai-visual-regression-smoke.js`
证据路径:
- B1 Wolai 基线:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task131-sidebar-b1-baseline/wolai-sidebar-b1-open-1392x1213.png`
- B1 本地最终:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task131-sidebar-b1-postfix/local-sidebar-b1-postfix-1392x1213.png`
- Phase B Wolai 基线矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-baseline/comparison-matrix.md`
- Phase B 本地主线程最终:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b/local-sidebar-phase-b-1392x1213.png`
- Phase B 本地菜单最终:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b/local-sidebar-phase-b-menu-1392x1213.png`
- Phase B subagent post-fix2 JSON`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-postfix2/sidebar-phase-b-postfix2-measurements.json`
- Phase B subagent Wolai 菜单:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-postfix2/wolai-sidebar-context-menu-1392x1213.png`
- Phase B subagent 本地菜单:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-postfix2/local-sidebar-context-menu-1392x1213.png`
- B11 用户指出差异截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-baseline/wolai-03-page-tree.png` / `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task132-sidebar-phase-b-baseline/local-03-page-tree.png`
- B11 subagent 基线矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task133-sidebar-tree-icons-arrows-baseline/comparison-matrix.md`
- B11 subagent Wolai / 本地复核截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task133-sidebar-tree-icons-arrows-baseline/wolai-sidebar-tree-icons-arrows.png` / `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task133-sidebar-tree-icons-arrows-baseline/local-sidebar-tree-icons-arrows.png`
- B11 本地主线程最终截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task133-sidebar-tree-icons-arrows/local-sidebar-tree-icons-arrows-1392x1213.png`
- B11 二次复核矩阵:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task134-sidebar-arrow-no-empty-slot-baseline/comparison-matrix.md`
- B11 二次复核截图:`/mnt/Data1T/mnote/tmp/wolai-editor-parity/task134-sidebar-arrow-no-empty-slot-baseline/wolai-sidebar-tree.png` / `/mnt/Data1T/mnote/tmp/wolai-editor-parity/task134-sidebar-arrow-no-empty-slot-baseline/local-sidebar-tree.png`
主线程复核结论:
- B1-B11 可量测项已对齐或按 checklist 明确保留本地差异。
- Wolai header/avatar DOM 选择器不稳定,最终以截图 + 本地稳定锚点复核。
- `Explorer` 是 mnote 本地创新入口,按 B10 保留;视觉不抢“我的页面”主路径。
- 菜单位置因页面树数据不同而不同;尺寸、项目、分组高度已对齐。
- Wolai 页面树图标是每行不同的私有 glyph;当前本地 projection 没有真实 per-page icon 数据,因此不硬编码类似图标,也不保留空白 icon 列。二次复核确认:保留空列会让标题 x 更接近 Wolai,但视觉上是明显缺失;当前按用户确认的产品判断去空列,残留差异是标题比 Wolai 真实图标+标题组合更靠左。后续若树投影提供真实 icon 字段,再按行渲染。
## 6. Phase C:顶栏、breadcrumb、搜索
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| C1 | TODO | 顶栏高度与按钮密度 | 顶栏约 40px;按钮轻量 hover;右侧 icon/text 序列不重 |
| C2 | TODO | breadcrumb | 图标、分隔符、弱色文字、截断方式对齐;切页后与页面树同步 |
| C3 | TODO | owner/published 按钮分型 | owner 与 published 的按钮组合分开;不把 published 文案硬塞 owner 态 |
| C4 | TODO | 顶栏搜索入口 | 点击打开全局搜索 modal,URL 不变,焦点进入输入框 |
| C5 | TODO | 侧栏搜索入口 | 打开同一个全局搜索 modal 或明确的树过滤入口;不得误跳 `/search` |
| C6 | TODO | 搜索 modal 默认状态 | `仅匹配标题``页面内搜索` 为 switch 且默认开启;不是普通 pill button |
| C7 | TODO | 搜索 modal 关闭路径 | Esc、遮罩、再次 `Ctrl+P`、入口 toggle 按 Wolai 实测对齐 |
| C8 | TODO | 搜索结果行 | 匹配数量、命中高亮、快捷键提示、结果 icon、所在页面信息对齐 |
| C9 | TODO | 搜索空态/加载态 | 输入无结果和加载时的视觉与 DOM 状态可被 smoke 捕获 |
| C10 | TODO | 搜索入口/快捷键矩阵 | 分别验证 Wolai 左侧搜索、顶栏/侧栏入口、`Ctrl+P``Ctrl+K`;每个入口记录 URL、打开/关闭路径和阻塞 |
| C11 | TODO | 搜索结果 fixture | 固定本地 `Hermes` 或等价种子数据,避免用随机工作区数据对比 Wolai 结果态 |
## 7. Phase D:文档画布与阅读态
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| D1 | TODO | 主内容列宽 | 普通文档列宽约 760px;大屏不贴左;移动端不溢出 |
| D2 | TODO | 标题位置与样式 | Hermes 标题 y 位置、`34px / 44px / 600`、颜色接近 Wolai |
| D3 | TODO | 默认隐藏 meta/debug 行 | 普通文档默认不显示不像 Wolai 的工作区 meta、横线、debug outline |
| D4 | TODO | 正文段落 | `16px / 24px`、段间距、弱色文本与 Wolai 对齐 |
| D5 | TODO | 任务块 | checkbox 尺寸、边框、文本 baseline、完成态接近 Wolai |
| D6 | TODO | 引用/列表/标题块 | 阅读态块样式不跳出 Wolai 密度;截图覆盖至少三类块 |
| D7 | TODO | 右下角浮动入口 | AI / 帮助入口减重,尺寸和位置接近 Wolai,不遮挡正文 |
| D8 | TODO | 阅读态和编辑态切换 | 切换时标题、正文列宽、scroll 位置不明显跳动 |
| D9 | PARITY | 普通文档首屏入口语义 | `task130` 已覆盖 RED/GREENsubagent 最终对标复核通过:根页不再停在“打开当前页面”入口,直接出现 document shell + editor island |
## 8. Phase E:主编辑器块交互
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| E0 | TODO | editable-test 焦点护栏 | 写入前先断言焦点在正文测试块,不在标题、breadcrumb 或隐藏输入;截图记录焦点位置 |
| E1 | TODO | editable-test 基础输入 | Hermes editable-test mode 取 Wolai 输入/回车/撤销基线;本地保存后刷新保持 |
| E2 | TODO | 块 hover 手柄 | hover 块只显示左侧手柄区,不自动弹菜单;浅色块 hover 背景对齐 |
| E3 | TODO | 上方插入短横线 | hover 短横线变 `+`tooltip `在上方插入块` / `Esc, a`;点击前插入 |
| E4 | TODO | 下方插入短横线 | hover 短横线变 `+`tooltip `在下方插入块` / `Esc, b`;点击后插入 |
| E5 | TODO | `Esc, a` / `Esc, b` | 块选中态快捷键插入 before/after;本地 smoke 验证焦点进入新块 |
| E6 | TODO | 块菜单 | 点击手柄打开菜单;`转换为 / 拷贝副本 / 删除 / 复制链接 / 颜色` 等入口视觉对齐 |
| E7 | TODO | slash 菜单 | 输入 `/` 跟随 caret 打开;搜索过滤和键盘选择可用 |
| E8 | TODO | selection toolbar | 选中文本出现工具条;粗体、斜体、链接、颜色、转换块入口可见 |
| E9 | TODO | `Ctrl+A` 双阶段选择 | 第一次选当前文本/块,第二次选全部块;行为与 Wolai 基线一致 |
| E10 | TODO | 拖拽预览 | 块拖动时横向 drop line、分栏竖线预览;持久化可后置但预览需对齐 |
## 9. Phase FPage Aggregate 和命令边界
| ID | 状态 | 任务 | 验收要点 |
| --- | --- | --- | --- |
| F1 | TODO | 标题单一真源 | 标题修改后 breadcrumb、sidebar、file tree、document title 同步 |
| F2 | TODO | 正文保存命令 | 编辑器保存继续走 `page.body.save`,不把 Tiptap 临时 id 当持久真相 |
| F3 | TODO | 页面设置语义 | 设置项进入 `page-option-semantics` 和 editor runtime,不做只保存不消费的假功能 |
| F4 | TODO | 页面树命令 | 新建、重命名、移动、归档仍沿 `tree.*`;菜单入口不直接操作本地临时数组 |
| F5 | TODO | 引用语义 | 页面引用、块引用、嵌入引用先保留类型边界,不降级成普通 URL |
## 10. 每轮收尾
每完成一个 ID,必须补齐:
- Wolai 截图路径:`TODO`
- 本地截图路径:`TODO`
- smoke / 测试命令:`TODO`
- 主线程复核结论:`TODO`
- 剩余差异:`TODO`
如果出现新的可复用失败模式,同步更新 `/home/lix/.codex/skills/wolai-aline/references/failure-patterns.md`