Phase A — EditorRuntimeActor 内存缓存层 - 新增 editor_actor.rs: EditorBlockDocument 内存态 + apply_command + load_or_init - block.rs 四个写工具(replace/insert/delete/move)接入 actor 路径 - editor_actor feature flag(MNOTE_WEB_ENABLE_EDITOR_ACTOR=true 默认开启) - bridge-runtime 三个核心函数公开化 - rust-toolchain: 1.89 → stable(修复 spike WASM 编译阻塞) Phase B — 编辑器增量 delta channel - BlockDelta/DeltaOperation 类型 + actor.build_block_delta() - leptos-tiptap spike: mnote:editor:block-delta CustomEvent 监听 + JSON patch - DocumentAiAgentPanel: 拦截 blockDelta → window dispatchEvent - 工具响应含 blockDelta 字段供前端消费 Phase C — 事件 stream delta - broadcast channel 在 AppState/actor/SSE 三层贯通 - tree_events SSE 端点发 block.delta 事件 - 旧客户端降级兼容 环境修复 - rustc recursion_limit = 1024(修复 Leptos SSR 类型深度溢出) - run-convex-deploy.js(封装 Convex function 部署到本地后端 3210) ref: design/07-ai/process/7-13-page-block-editor-runtime-actor-v1.md
31 KiB
5-6 [process] Page Aggregate 单一真源对齐执行清单 v1
更新时间:2026-05-09
关联文档:
/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-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 已经成立的事实
leptos-tiptap已经成为页面内正式主编辑区,不再是 iframe bridge。- 文档页正式读链已固定消费 Rust
mnote.page_aggregate.v1snapshot,TS builder 已退出 runtime 主链。 - 正文保存已经能按
workspaceId/documentId正确落到对应页面。 - 当前主编辑区已具备可继续推进的基础交互能力。
- 当前问题已经不再是“能不能接入主编辑器”,而是“接入后如何收口为单一真源”。
2.2 还没有成立的事实
- 页面树 / 文件树 / 页面头部 / 页面设置 / 主编辑区还没有消费同一份 page aggregate projection。
- 标题 / 正文 / 页面设置已经开始统一到同一组 page aggregate command family。
pageOptions还没有整体收口到leptos-tiptapisland 的正式运行时语义层。- 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 设计层完成标准
- 单独补一份 page aggregate contract 文档。
- 文档中固定
page_identity / page_head / page_layout / page_body / page_tree的最小字段。 - 文档中固定哪些字段属于 page aggregate truth,哪些字段只是前端 UI state。
- 文档中固定哪些字段必须由 Rust 主导,哪些字段允许作为前端临时态存在。
4.2 代码层完成标准
- 前端与 Rust 侧都出现统一命名的 page aggregate 类型或等价契约。
- 不再继续给
DocumentPageProps、DocumentContentProps零散加字段来扩页面真相。 - 文档页加载入口能明确区分“聚合读取结果”和“局部 UI 临时态”。
补充:当前 page-aggregate-loader.ts、文档页 SSR 入口与 DocumentContent 的内容重试补拉都已经直接消费 Rust /api/page-aggregate/:id 返回的 PageAggregateProjection,不再由 page.tsx 手工拼 meta + content。Next /api/documents/page 已降级为显式 410 的 compat 边界;page-aggregate-loader.ts 现在只校验并消费这条 Rust 正式读链,不再在 runtime 中回退到 TS builder。与此同时,storage-convex-bridge 与 bridge-runtime 已开始接受 page.head.updateTitle / page.layout.updateOptions / page.body.save 这组 page command family 的命名口径,因此这里按“读取契约已进入 Rust-first,写入命令面已开始收口”勾选完成。
补充:这里的“Rust-first”只表示 /api/page-aggregate/:id 的对外 route、契约校验和前端消费主链已经收口到 Rust,不表示底层已经完全 kernel-native。2026-05-13 复核后,当前 route 仍由 Rust runtime adapter 消费 documents:getMeta + documents:getContent substrate 构建聚合快照,因此 response source / x-mnote-page-aggregate-owner 应反映为 CompatMetaContentJoin / compat-join。只有底层真实改为 kernel 原生 page aggregate projection 后,才应标记为 KernelProjection / rust-kernel。
补充:2026-05-16 复核 page-aggregate-loader.ts、page-aggregate-builder* 与 /api/documents/page compat route 后,确认读取主链仍然只消费 Rust /api/page-aggregate/:id,TS builder 仅保留为历史 adapter / 单测材料,Next /api/documents/page 继续明确返回 410。验证命令:cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate、cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate、cd wolai-frontend && pnpm test src/app/api/documents/page/route.test.ts src/lib/documents/page-aggregate-builder.test.ts。全仓 git diff --check 因既有删除的 rust/spikes/leptos-tiptap-spike/trunk-8123.err 无法生成 checkdiff,本轮未处理该无关脏改动。
补充:2026-05-16 在 http://127.0.0.1:3000 的 Rust mnote-web 主入口实跑 Page Aggregate smoke。task110-page-title-single-truth-smoke.js 验证标题修改后页头、Breadcrumb、Sidebar、Page Tree、File Tree 与刷新后标题一致,证据为 tmp/page-aggregate-single-truth-smoke/20260516-182244/task110.stdout.json。文档打开 smoke 验证文档 HTML 包含 data-page-aggregate-snapshot="mnote.page_aggregate.v1" 与 data-page-tree-source="page_aggregate.tree.pageSubtree",同一临时页 /api/page-aggregate/:id 回读 schema=mnote.page_aggregate.v1,证据为 tmp/page-aggregate-single-truth-smoke/20260516-182244/page-open-snapshot.stdout.json 与 page-open-snapshot.png。字段完整性 smoke 验证 identity/head/body/tree/stats 全部存在,证据为 tmp/page-aggregate-single-truth-smoke/20260516-182244/page-aggregate-fields.stdout.json。当前 projectionSource=documents.content,仍符合“Rust-first 读取链、非 kernel-native 落库完成态”的过渡口径。
补充:2026-05-16 继续新增 scripts/task-page-aggregate-body-sync-smoke.js,验证真实页面正文编辑后 /api/documents/save 与 /api/page-aggregate/:id 回读闭环。证据为 tmp/page-aggregate-body-sync-smoke/mp87mgz7.json 与 mp87mgz7.png:body.revision 从 0 到 1,body.conflictDetectionKey 从 tree_1778927703753_1:0 到 tree_1778927703753_1:1,body.blockDocument.blocks[0] 回读到新段落文本和 revisionRef。配套命令 cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command 与 cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_projects_legacy_content_to_block_document 已通过。
4.3 退出标准
- 后续再讨论标题、页面设置、正文保存时,能够直接定位到它属于
page_head / page_layout / page_body / page_tree的哪一层。
5. Phase G:主文档页改为消费统一聚合
目标:
让
/documents/[id]不再手工拼title + options + content + pageSubtree,而是消费统一页面聚合。
5.1 加载链路收口
- 页面 SSR / route 入口改为优先读取统一 page aggregate,而不是分别读取 meta 和 content。
DocumentShell/DocumentContent的入参改为围绕统一聚合对象组织。- 页面头部、页面设置初值、主编辑区 bootstrap、TOC/outline 都来自同一份聚合结果。
5.2 当前散落 props 的收口
title不再作为独立真相字段散落传递。initialOptions不再作为独立真相字段散落传递。initialContent、initialRevision、initialConflictDetectionKey归入统一page_body。initialPageSubtree归入统一page_tree,并明确其是正式 projection 还是仅兼容过渡项。
5.3 退出标准
- 页面加载时不再能明显看出“这是几份子结果拼出来的页面”。
- 后续页面新增字段时,不再需要继续向外层 props 链同时塞多种局部真相。
补充:当前首屏 SSR 与客户端内容重试补拉都已经直接走 /api/page-aggregate/:id -> PageAggregateProjection,因此“页面明显由 meta + content 两次查询拼起来”的入口级痕迹已经消失;但新增字段仍可能需要继续补 loader / route / island 消费链,所以第二项继续保留未完成。
补充:入口级痕迹消失不等于来源语义消失。当前前端 loader 仍只接受 mnote.page_aggregate.v1,不会恢复 TS builder 主链;但 Rust response 的 provenance 必须继续暴露真实构建来源,避免把兼容 join 误验收成 kernel-native projection。
补充:本轮已新增 page-aggregate-client-state.ts,并让 DocumentContent 把原先分散维护的 title / options / content / serverContentSnapshot / serverPageSubtreeSnapshot / contentRevision / conflictDetectionKey 继续收口为同一份 client aggregate state reducer;标题的 draft / committed / persisted 语义也已并入这份 reducer,不再额外维护独立 usePageHeadTitle hook 或 serverPageSubtreeTitle 双轨状态。这里代表“页面本地 head/body/layout/tree 真相已经开始统一”,不等于页面设置运行时分类、AI 对页面设置面的正式入口都已闭环,因此本阶段不提前宣称聚合完成。对应最小回归测试为 page-aggregate-client-state.test.ts、document-content.test.ts。
6. Phase H:让 pageOptions 真正进入 leptos-tiptap island
目标:
把当前“能展示、能保存、但主编辑区不真正消费”的页面设置,按优先级接入 island。
6.1 必须先分类
- 把当前页面设置分成:页面壳布局类、阅读视图类、编辑器 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_runtimeshowHeadingNumbers属于read_view + editor_runtimeshowToc属于read_viewprotectEditing / showBlockRefCount属于editor_runtime,但当前仍是plannedembedDefaultBlockId已进入editor_runtime
对应最小回归测试为 page-option-semantics.test.ts、leptos-tiptap-island-editor-host.test.tsx、page-options-sidebar.test.tsx。第二项继续保留未完成,因为“哪些项应降级/隐藏/保留”的产品面纠偏还没完全落到 inspector 与 AI tool surface。
补充:当前统一口径已经开始落到代码级 PAGE_OPTION_PANEL_POLICY,最小分类如下:
- 保留在页面设置面板且继续作为正式项:
wideLayout / smallText / showHeadingNumbers / showToc / showWordCount / collapseBacklinks / pageFont / layoutDensity / hideChildPages / embedDefaultBlockId - 保留但降级展示、不再冒充正式完成项:
protectEditing / showBlockRefCount - 不再作为页面级正式项,继续留在全局偏好:
showStructure
这里的“保留 / 降级 / 全局”是当前 Inspector、AI 页面设置写回白名单与后续 smoke 验收都必须共用的同一份产品口径,不允许再由单个 UI 文件私自重写说明。
6.2 最小接入优先级
wideLayout真正进入 island 布局语义,而不是只改外层壳宽度。smallText真正进入主编辑区排版语义。layoutDensity真正进入块间距 / 正文密度语义。showHeadingNumbers真正进入 heading 展示语义,或明确标注暂不支持。embedDefaultBlockId真正进入主编辑器引用 / 嵌入默认位置逻辑,或明确标注暂不支持。
6.3 页面设置面板纠偏
- 没有真正接入 island 的编辑器语义项,不再继续以“已开启/已关闭”假装正式完成。
- 对未支持项给出显式降级说明,而不是仅保存字段。
- 让“页面设置有值但编辑器内部没变化”这类状态在产品上消失。
补充:本轮已把 AI 页面设置结构化结果接入 DocumentContent 现有的 patch_page_options + page.layout.updateOptions 正式链路,页面设置不再只能靠人类点击 inspector 才能进入同一条页面命令面。同时,AI 正式写回白名单已限制为 page-option-semantics.ts 中 runtimeSupport === "wired" 的字段,protectEditing / showStructure / showBlockRefCount 继续排除在正式写回外,避免把 planned / ui_only 选项误描述为稳定能力。历史 mnote-cli host 侧也已新增最小服务端分支:命中页面设置 patch 时,直接执行 page.layout.updateOptions 并产出结构化 tool_result(action=update_page_options),不再只把整段结果退化成 assistant 文本。2026-05-13 起后续主线改为 Hermes tool call -> mnote skill/plugin -> page.layout.updateOptions,该 host 分支只保留为过渡证据。对应最小回归测试为 DocumentAiAgentPanel.runtime.test.tsx、mnote-cli-agent-host.test.ts。
补充:本轮继续把 Inspector 产品态与这份分类对齐:showHeadingNumbers / embedDefaultBlockId 已按“正式接通项”更新文案,不再继续显示“已保存字段但未接通”;showBlockRefCount 作为纯占位项也不再继续暴露可点击的假开关,而是明确显示 待接线。对应最小回归测试为 page-options-sidebar.test.tsx、page-option-semantics.test.ts。
6.4 退出标准
- inspector 中至少最小优先级项修改后,主编辑区可见结果真实变化。
- 用户不再需要猜“这个设置到底有没有真正作用到编辑器”。
补充:本轮已新增 scripts/task164-page-options-visible-effect-smoke.js,并在 http://127.0.0.1:3000 实跑确认:
wideLayout从false -> true后,页面根属性data-page-wide-layout立即切到true,主内容列最大宽度从默认态切到980pxsmallText从false -> true后,编辑器字号从16px变为15pxlayoutDensity从normal -> compact后,段落底部间距从8px变为4px
同时,Inspector 文案与状态也已经按“正式接通 / 待接线 / 全局项”统一,不再让用户靠猜测判断设置是否真正生效。
补充:2026-05-16 继续新增 scripts/task-page-aggregate-options-sync-smoke.js,在 http://127.0.0.1:3000 的 Rust mnote-web 主入口实跑页面设置写入与 Page Aggregate 回读闭环。该 smoke 新建临时页后依次通过页面设置 UI 修改 wideLayout=true、smallText=true、layoutDensity=compact,确认 /api/documents/options 返回 page.layout.updateOptions,随后轮询 /api/page-aggregate/:id 直到 layout.pageOptions 同步,并同时断言 island/runtime DOM 属性同步。证据为 tmp/page-aggregate-options-sync-smoke/mp87y6j3.json 与 tmp/page-aggregate-options-sync-smoke/mp87y6j3.png:Page Aggregate 回读到 wideLayout=true / smallText=true / layoutDensity=compact,运行时同步为 data-page-wide-layout="true"、data-page-small-text="true"、data-layout-density="compact",编辑器字号从 16px 到 15px,段落间距从 8px 到 4px。配套命令 cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options 与 cd wolai-frontend && pnpm test src/lib/documents/page-command-client.test.ts src/components/editor/leptos-tiptap-island-editor-host.test.tsx src/lib/documents/page-option-semantics.test.ts 已通过。
补充:2026-05-16 继续新增 scripts/task-page-aggregate-refresh-persistence-smoke.js,在同一临时页内通过页面头、正文编辑和页面设置 UI 依次写入最新值,等待 /api/page-aggregate/:id 回读最新 head/body/layout 后刷新页面,再断言标题、正文、页面设置和 runtime DOM 不回退。证据为 tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.json 与 tmp/page-aggregate-refresh-persistence-smoke/mp88fr6k.png:刷新前后 head.title=Page Aggregate refresh mp88fr6k、body.revision=1、conflictDetectionKey=tree_1778929070007_1:1、blockDocument.blocks[0].text=Page Aggregate refresh body mp88fr6k、layout.pageOptions.wideLayout/smallText/layoutDensity=true/true/compact 均保持稳定;刷新后页头、.ProseMirror 正文、设置控件、documentElement、.document-shell、island root 和 .editor-surface 都保留最新值。修复点为 rust/crates/mnote-web/src/ssr/pages/layout.rs 将 initializePageUiSurfaces 延后到 DOMContentLoaded 后执行。配套命令 cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate 与 cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_options_route_executes_page_layout_update_options 已通过。
7. Phase I:树域与页面域标题统一
目标:
rename 后页头、sidebar row、page tree、file tree 使用同一份标题结果。
7.1 标题真相纠偏
- 页面头部标题不再长期由前端
pageTitle本地状态冒充正式真相。 - rename 命令在语义上进入统一 page aggregate command family。
- 树域消费的标题与页面头部消费的标题来自同一份更新后的 projection。
7.2 回归验证
- 当前页重命名后,页头即时更新。
- sidebar 对应节点标题同步更新。
- page tree / file tree 对应节点标题同步更新。
- 刷新后标题一致,不依赖额外手动刷新。
- 切页往返后标题一致,不出现旧快照回闪。
注:当前已在标题持久化成功后广播 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,并把文档页头标题的本地 draft / committed / persisted 语义直接并入 page-aggregate-client-state;DocumentContent 现在只从同一份 preferred sidebar snapshot 读取 live committed title,再与聚合 reducer 内的短暂 draft / persisted title 做合成,不再额外维护独立标题 hook。与此同时,useSidebarData.refetch() 在 Convex live 模式下收到 documents-changed 也会主动拉取一份新的 /api/sidebar snapshot,再与 tree stream 做 freshness 选择,避免“页头草稿是新的,但 breadcrumb / sidebar / page tree / file tree 还卡在旧快照”。对应单测为 page-aggregate-client-state.test.ts、use-sidebar-data.test.tsx。浏览器烟测 scripts/task110-page-title-single-truth-smoke.js 已验证:页头重命名后,breadcrumb、默认 sidebar、page tree、file tree、切页往返、刷新均保持一致,因此本阶段与“标题来自同一份更新后的 projection”相关的勾选正式保留。
补充:2026-05-14 已把“文件树 / 页面树无需刷新可见”的验收口径固定到 Rust 3000 主文档壳:MNOTE_UI_BASE_URL=http://127.0.0.1:3000 node scripts/task426-mnote-web-main-no-reload-smoke.js 覆盖 /documents/<id> 中页面 create、页面 delete,以及页面内新建 mindmap 后 File Tree asset row 即时出现。后续不能再用 3001/Next 路径或 /tree debug route 代表用户实际使用的 3000 主入口;页面内 mindmap create 是 /api/mindmap/<doc>/<mindmap> 资产写入链路,必须和页面 tree.node.create/delete 分开验收。
7.3 退出标准
- 标题不同步问题不再以“前端本地状态漂移”的形式重复出现。
8. Phase J:AI 写入口对齐 Page Aggregate
目标:
AI 不再绕过 page aggregate,直接拼前端对象或编辑器内部格式。
8.1 语义边界
- 明确 AI 写页面时优先进入哪组 page aggregate command。
- 明确 AI 写标题、写正文、改页面设置、插入引用时分别走哪些统一命令语义。
- 明确 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。2026-05-14 起同类上下文的当前主线已改为 Hermes run/session context,并由 Hermes 通过 mnote plugin 回读最新 page aggregate;页面设置写入也已由 mnote.page.update_options 回到 page.layout.updateOptions,旧 /api/ai-agent/run 证据只保留为历史过渡记录。
8.2 与主编辑区的关系
- 人类编辑与 AI 编辑共享同一块语义边界。
- AI 改写结果能通过主编辑区 island 正式回显。
- AI 改写后树标题 / 页面头部 / 页面设置不再走各自独立副作用链。
补充:进入这一步时,最大的真实 blocker 已经明确下来;以下为历史 blocker 记录,不再作为 2026-05-14 之后的当前状态描述:
- 页面 AI 历史实现当时仍残留
/api/ai-agent/run/mnote-cli host路径 - Hermes 页面内客户端、Hermes session 真相和 mnote skill/plugin tool surface 当时还没有在页面 AI 主链落地
- 因此,当时新增的
page_options_patch -> page.layout.updateOptions -> structured tool_result只是“历史主路内的最小结构化兼容分支”,不是完整正式 Hermes tool surface
这意味着 8.2 后续完成标准必须至少包含:
- 页面设置进入 Hermes tool call -> mnote plugin -> Rust runtime 的正式执行环,而不是继续靠 host 内部的自然语言/最小规则分支识别
- 页面设置结构化结果由正式 mnote plugin tool 调用产出,而不是只由 host 自己补一条兼容
tool_result - 树标题 / 页头 / 页面设置三条 AI 写回链在同一条正式 page aggregate command family 中闭环,并补 smoke 验证
2026-05-14 追加说明:上述 blocker 中“页面 AI 历史实现仍残留 /api/ai-agent/run / mnote-cli host 路径”和“Hermes 页面内客户端、Hermes session 真相、mnote skill/plugin tool surface 还没有落地”的判断已被 design/07-ai/done/7-4-page-ai-hermes-panel-execution-checklist-v1.md 覆盖。当前代码已新增 Hermes client proxy 与 mnote Hermes tool routes,页面 AI 主链已改为 Hermes session/run/events,标题、页面设置、正文、artifact 写入已回到 Rust runtime / Page Aggregate command family;旧 /api/ai-agent/run 在 mnote-web 中已收口为 legacy_ai_agent_run_retired guard。7-4 的正式 3000 smoke 矩阵与持久化审计已通过,剩余文档治理以 7-4 的 N 项为准。
8.3 退出标准
- 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 事件当作薄日志。2026-05-14 起这只保留为过渡证据;新的主线已由 Hermes 发起 mnote plugin tool call,再由 Rust runtime / kernel 返回 tool result。其后:
doc_insert_blocks / doc_replace_range继续按page.body.save语义落到/api/documents/save,再正式回显主编辑区 island。slash_run(rename current page)会把结构化结果回接到当前页DocumentContent的同一条标题提交链,并继续广播emitDocumentsChanged(documentId),因此页头标题与树标题不再靠 AI 面板内部本地状态各自漂移。- 当前 AI 面板已经能消费结构化
update_page_options结果,并把pageOptionsPatch回接到当前页DocumentContent的同一条patch_page_options + page.layout.updateOptions提交链;同时只允许runtimeSupport === "wired"的字段进入正式写回,避免 planned / ui_only 页面设置混入主链。对应最小回归测试为DocumentAiAgentPanel.runtime.test.tsx、document-content.test.ts。 - 历史
mnote-cli host能在命中页面设置 patch 时直接执行page.layout.updateOptions,并向前端回放结构化tool_call/tool_result事件;这意味着“服务端完全没有页面设置结构化写回结果”的状态已经结束。 - 当前
pageOptions已进入 mnote Hermes plugin 的第一批正式 tool surface,mnote.page.update_options只允许runtimeSupport === "wired"字段写入page.layout.updateOptions,planned / ui_only 字段返回 warning 或 ignored 结果。后续剩余项不再是“页面设置类 AI 命令是否闭环”,而是 slash AI / selection toolbar AI 等更细粒度入口如何复用同一条 Hermes tool chain。
9. 推荐实施顺序
当前推荐顺序固定为:
Phase FPhase GPhase HPhase IPhase J
约束如下:
- 没有完成
Phase F之前,不再继续零散补标题 / 宽度 / 页面设置。 - 没有完成
Phase G之前,不把当前文档页描述成“已经统一聚合完成”。 - 没有完成
Phase H之前,不把页面设置大量打钩为“正式可用”。 - 没有完成
Phase I之前,不把标题问题视为已从底层解决。 - 没有完成
Phase J之前,不把 AI 直接写主编辑区描述成已经具备正式稳定入口。
10. 当前阶段的总退出标准
只有当下面这些条件同时成立时,才可以把这条线描述成:
“主编辑区与树域已经基本统一到 Rust 主导的 page aggregate 单一真源架构”。
- 文档页消费统一
page aggregate projection - 标题 / 正文 / 页面设置不再是三条分裂真相链
leptos-tiptapisland 真正消费 editor-relatedpage_layout- 树标题与页头标题来自同一份 projection
- AI 写入口开始围绕 page aggregate command family 实现
补充:当前未勾选“标题 / 正文 / 页面设置不再是三条分裂真相链”的原因,不再只是命令名或 route 分裂。虽然前端页面写入口、Next route 执行器、DocumentContent 内部的 body/layout/tree client state、以及 pageOptions 的代码级运行时分类都已经开始收口,但 projection 回流与 AI 对页面设置面的正式写入口仍未完全统一。也就是说,命令执行面、页面本地状态面、页面设置语义面都已开始统一,但页面域单一真源仍未闭环。
在此之前,正确口径都应保持为:
leptos-tiptap主编辑区已基本可用,但页面聚合仍未收口,当前仍处于从混合态向 Rust 单一真源过渡的过程中。