收口 MNote P0 P1 P2 审查尾项

- 归档 OnlyOffice live bridge、Page AI、mindmap、design governance 与相关 bug 条目
- 补齐 MinerU OCR 后端 runtime 合同与 smoke/test 基线
- 收口 ChatOnly/Doubao、ObjectIdentity、Page Aggregate compat 与 runtime owner 文档口径

验证:
- cargo test --manifest-path rust/Cargo.toml -p mnote-web local_ocr -- --test-threads=1
- cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_bridge -- --test-threads=1
- git diff --check
- git diff --cached --check
- codegraph index . --force && codegraph status .
- codegraph sync . && codegraph status .
This commit is contained in:
lix-2026
2026-06-01 09:29:12 +08:00
parent 49a0545148
commit 1882db7681
143 changed files with 29810 additions and 3228 deletions
@@ -0,0 +1,121 @@
# Office local-first 预览、编辑与插件噪音缺口审查 v1
## 状态
- 状态:done
## 背景
本轮只审查 OnlyOffice 与 MNote local-first 工作区之间的三个接口缺口:
1. `/api/media/sign` 对 local-folder asset 返回 404。
2. Office 附件默认只读,但需要三点菜单提供“使用编辑模式打开”。
3. OnlyOffice 自定义 annotation 插件存在 404 / pageerror 噪音。
上一轮浏览器验证已经确认:中文 docx 上传后可在 resource tab 中只读打开,`errorCode=-18` 的 WebSocket 连接问题已通过 doc key 安全化和 `/onlyoffice-server` 反代修复。因此本轮不把“只读预览可见”重新定义为失败。
## 官方依据
Context7 查询 `/onlyoffice/api.onlyoffice.com` 得到的关键口径:
- `editorConfig.mode` 支持 `"view"` / `"edit"`,只是初始化编辑器模式。
- 保存必须配置 `editorConfig.callbackUrl`,后端在 callback `status === 2``status === 6` 时下载 `body.url` 并写回文件,成功返回 `{ "error": 0 }`
- `onRequestEditRights` 是 view 模式切 edit 的官方事件;触发后集成方必须以 edit mode 重新初始化编辑器。
- 插件通过 `editorConfig.plugins.autostart``editorConfig.plugins.pluginsData` 连接;annotation / custom assistant 类插件 404 属于可选插件加载链路,不等于主文档渲染失败。
## 代码现状
- `rust/crates/mnote-web/src/routes/media.rs``/api/media/sign` 当前只服务 Convex media asset,内部查询 `mediaAssets:getById` / `mediaAssets:refreshUrl`。local-folder asset 形如 `local:asset:<path>`,不在 Convex media 表中,所以返回 404 是现状契约不匹配。
- `rust/crates/mnote-web/src/routes/onlyoffice.rs``/onlyoffice` 页面在 `resolveAssetUrlAndKey()` 中只要有 `assetId` 就请求 `/api/media/sign`;失败后静默保留传入的 `fileUrl`。这就是“当前 view 模式不阻断打开”的原因。
- local-folder Office 预览已经可以直接使用 `/api/local-folder/files/open?rootUri=...&path=...` 作为 `fileUrl`,不需要经过 `/api/media/sign`
- `/api/onlyoffice/callback` 已由 `5-36` 补齐 local-folder `status 2/6 -> 下载 body.url -> 原文件覆盖写回` 的后端契约;legacy Next 代理仍保留为 cloud/compat 边界。
- `layout.rs` / browser runtime 已提供“使用编辑模式打开”入口,默认打开保持 `mode=view`,显式 edit 保留 guard。
- annotation/custom assistant 插件噪音已由 `5-37` / `5-40` 分类:插件噪音不等于主文档失败,真实 `errorCode=-18`、WebSocket 失败或 iframe 空白不能被噪音掩盖。
## 根因判断
### 1. `/api/media/sign` local-folder 404
根因不是权限失效,而是端点 ownership 错位:`/api/media/sign` 是历史 Convex media asset 签名端点,local-folder 文件已经有 local open route,二者不应混用。
P0 目标不是让 `/api/media/sign` 接管所有本地文件,而是让 OnlyOffice local 路径不再无意义请求该端点,避免误导日志和后续测试。
### 2. 编辑模式入口
OnlyOffice `mode=edit` 并不等于 MNote 已支持保存。没有 callback 写回闭环时,直接默认编辑会制造“看似能改、实际不能保存”的假功能。
P0 目标:
- 默认打开仍为 `view`
- 菜单提供“使用编辑模式打开”入口。
- 编辑入口必须带 guard:清楚标记为实验能力,或在 local-folder writeback 未闭环时阻止/提示。
P1 目标是实现 local-folder callback 写回;该项已由 `5-36` 完成后端契约。
### 3. annotation 插件 404 / pageerror
插件链路属于可选增强。若配置了 autostart / pluginsData 但静态资源不存在,会产生 404 或 pageerror。只要正文渲染和 OnlyOffice 主 WebSocket 正常,这不是打开失败。
P0 目标:
- 不把可选插件 404 当成 Office 打开失败。
- 不让缺失插件反复污染测试结论。
- 若当前 MNote 并未真正依赖 annotation 插件,则应禁用 autostart 或明确过滤为 non-critical。
## Checklist 拆分
- `5-34-local-folder-media-sign-office-url-contract-v1.md`
- P0OnlyOffice local-folder 打开不再请求 `/api/media/sign`
- P1:补回归测试,确认 local asset 仍可只读打开。
- `5-35-office-edit-mode-menu-and-guard-v1.md`
- P0:正文附件与文件树资源菜单增加“使用编辑模式打开”。
- P0:默认打开保持只读。
- P0:若保存闭环未完成,编辑入口必须有 guard / 实验标记。
- `5-36-onlyoffice-local-edit-save-callback-contract-v1.md`
- P1local-folder callback 写回。
- P1:覆盖 status 2/6、下载 URL rewrite、路径权限、冲突保护。
- `5-37-onlyoffice-annotation-plugin-noise-policy-v1.md`
- P0:定位 annotation 404 来源。
- P0:禁用无效 autostart / pluginsData 或把该类错误降级为 non-critical 测试噪音。
## 不做事项
- 不默认以 edit 模式打开 Office。
- 不在没有保存闭环时承诺“Office 可编辑保存”。
- 不把 local-folder 文件任意暴露给 `/api/media/sign`,除非经过 rootUri / workspace / allowed roots 校验。
- 不把 annotation 插件 404 等同于主文档渲染失败。
## 验收要求
- `cargo test -p mnote-web -- --test-threads=1`
- `codegraph sync .` 后确认索引健康。
- Reasonix clean browser 测试:全新 browser context,登录测试账号,上传 `/home/lix/Downloads/重庆发展特殊化妆品可行性报告_政府汇报版.docx`,打开默认只读,截图确认内容可见;打开三点菜单确认编辑入口;点击编辑入口后截图和日志证明行为符合 guard / edit URL 预期。
- Codex 必须复核 Reasonix 的 `result.json` 和截图,再自行做一次浏览器截图核查。
## 当前执行结论
- P0 已处理:
- local-folder Office 打开跳过 `/api/media/sign`,继续使用 `/api/local-folder/files/open`
- Office 默认打开为 `mode=view`
- 菜单提供“使用编辑模式打开”,进入前有实验 guard。
- 已存在 Office resource tab 从 view 切 edit 会刷新同一 iframe URL,不再只激活旧 tab。
- annotation/custom assistant 插件 404 已归类为 non-critical noise,不作为主文档打开失败。
- P1 已拆分并收口:
- `5-36`local-folder Office edit/save callback 写回后端契约已完成,覆盖 status 2/6、非写状态、未认证、路径越界。
- `5-35``onRequestEditRights` 事件会重新初始化为 `mode=edit` URL,不只 reload 当前 view。
- 本 umbrella review 仅作为缺口审查和拆分记录归档;后续真实 Office 编辑保存端到端浏览器验收若发现新问题,应另建具体 bug。
- 浏览器证据:
- Reasonix`tmp/reasonix-office-view-edit-plugin-2026-05-21/result.json` 与截图。
- Codex`tmp/codex-office-view-edit-plugin-2026-05-21/result.json``01-office-view-mode.png``02-office-edit-mode.png`
- 2026-06-01 验证:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1`
- `node --check scripts/task463-onlyoffice-resolver-smoke.js`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task463-onlyoffice-resolver-smoke.js`
- `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
- `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
@@ -0,0 +1,46 @@
# 5-34 local-folder media/sign 与 Office URL 契约
## 目标
OnlyOffice 打开 local-folder asset 时不再向 `/api/media/sign` 发起无意义请求;local 文件继续通过 local-folder open route 进入 OnlyOffice 只读预览。
## 原因
`/api/media/sign` 当前只签 Convex media asset。local-folder asset 已有 `rootUri + path` 的本地文件打开链路,不应混入 Convex 签名端点。
## 允许修改
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- 相关 `mnote-web` 单测
## 禁止事项
- 不开放任意本地路径签名。
- 不把 `/api/media/sign` 扩成绕过 allowed roots 的本地文件下载端点。
- 不改变非 local Convex media asset 的签名行为。
## Checklist
- [x] `/onlyoffice` 页面识别 local asset / local fileUrl,跳过 `/api/media/sign`
- [x] local asset 的 doc key 仍稳定、安全,不包含 `/``:`、中文等危险字符。
- [x] 非 local asset 仍可走 `/api/media/sign` 解析 signedUrl。
- [x] 补单测覆盖 local asset 不依赖 media sign 的页面脚本契约。
## 验收
- `cargo test -p mnote-web onlyoffice -- --test-threads=1`
- `cargo test -p mnote-web -- --test-threads=1`
## 本轮执行记录
- 2026-05-21Reasonix worker A 执行完成。
-`page()` 模板 JS 中添加 `isLocalFolderAsset()` 守卫函数。
- `resolveAssetUrlAndKey()` 中 local-folder asset 跳过 `/api/media/sign`
- 守卫条件:`assetId` 前缀为 `local:``local-file:`,或 `fileUrl` 路径含 `/api/local-folder/files/open`
- 新增 `onlyoffice_page_skips_media_sign_for_local_folder_asset` 单测。
- `cargo test -p mnote-web onlyoffice -- --test-threads=1` 全部 13 项通过。
- 2026-05-21Codex 复核补充。
- 干净浏览器上下文上传并打开 `/home/lix/Downloads/重庆发展特殊化妆品可行性报告_政府汇报版.docx`
- local-folder iframe `fileUrl` 指向 `/api/local-folder/files/open?...`,未出现 `/api/media/sign` local 404。
- 证据:`tmp/codex-office-view-edit-plugin-2026-05-21/result.json`,截图 `01-office-view-mode.png` / `02-office-edit-mode.png`
@@ -0,0 +1,80 @@
# 5-35 Office 编辑模式菜单与保护
## 状态
- 状态:done
## 目标
Office 文件默认只读打开;在正文附件三点菜单和文件树资源右键菜单中增加“使用编辑模式打开”入口。保存闭环未完成前,编辑入口必须有 guard 或明确实验标记。
## 原因
ONLYOFFICE `mode=edit` 只是编辑器初始化模式。若 MNote 没有完成 callback 写回,默认 edit 会让用户误以为修改已经保存到本地文件。
## 允许修改
- `rust/crates/mnote-web/src/ssr/pages/layout.rs`
- `rust/crates/mnote-web/src/routes/onlyoffice.rs` 中与 view/edit 初始化事件相关的最小补充
- 相关 `mnote-web` 单测
## 禁止事项
- 不默认 edit。
- 不声称 edit/save 已完整支持。
- 不改动 ACP / Hermes 无关代码。
## Checklist
- [x] `buildOnlyOfficeAssetOpenUrl` / local Office 默认输出 `mode=view`
- [x] 正文附件菜单增加“使用编辑模式打开”。
- [x] 文件树 Office asset 菜单增加“使用编辑模式打开”。
- [x] 编辑模式入口明确使用 `mode=edit` 打开到主 resource tab 或新窗口,行为与现有打开目标一致。
- [x] 在 local-folder save callback 未闭环时,编辑入口有 guard:可提示“编辑保存仍在实验中”,或通过 data/status 标记便于测试识别。
- [x] 已存在 Office resource tab 从 `mode=view` 切到显式 `mode=edit` 时,刷新同一 tab 的 iframe URL,而不是只激活旧 tab。
- [x] 若启用 `onRequestEditRights`,必须重新初始化为 edit URL,不只 reload 当前 view。
## 验收
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1`
- 浏览器截图:默认打开是只读;菜单中存在编辑入口;点击编辑入口后的页面 URL / debug state 包含 `mode=edit` 或 guard 提示。
## 本轮执行记录
- 2026-06-01Codex 补齐 `onRequestEditRights` P1。
- `/onlyoffice` 页面新增 `editModeLocationHref()`,触发 `onRequestEditRights` 时将当前 URL 的 `mode` 设置为 `edit``window.location.replace(editHref)`,避免只 reload 当前 view。
- 新增 `onlyoffice_page_reinitializes_edit_url_on_request_edit_rights` 契约测试,断言事件处理、debug 标记和 edit URL replacement 存在。
- 复测 `task463` 时发现并修复普通 `openTarget="new-window"``forceNewWindow || forceEditMode` 误判成 `mode=edit` 的状态泄漏;现在只有 `forceEditMode` 才会进入 edit,普通新窗口继续默认 view / `/office-preview`
- 验证通过:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_page -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1`
- `node --check scripts/task463-onlyoffice-resolver-smoke.js`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task463-onlyoffice-resolver-smoke.js`
- `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
- `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
- 2026-05-21Reasonix worker B 卡在计划阶段后由 Codex 终止;本条实现来自其它 Reasonix 结果与 Codex 复核修正。
- `buildOnlyOfficeOpenUrl` 默认 mode 从 `'edit'` 改为 `'view'`
- `buildOnlyOfficeOpenPath` 默认 mode 从 `'edit'` 改为 `'view'`
- `buildLocalOnlyOfficeOpenUrl` 默认 mode 从 `'edit'` 改为 `'view'`
- `normalizeOnlyOfficeAttachmentHref` 默认 mode 从 `'edit'` 改为 `'view'`
- `detailFromEditorAttachmentLink` 默认 mode 从 `'edit'` 改为 `'view'`
- `enhanceEditorAttachmentLink` 默认 mode 从 `'edit'` 改为 `'view'`
- 正文附件菜单(`openTreeContextMenu` `isAttachment` 分支)增加菜单项 `{ action: 'open-edit-mode', icon: 'edit_note', label: '使用编辑模式打开' }`,放置于"在新窗口打开"之前。
- 文件树 asset 菜单(`isAsset` 分支)增加相同的 `open-edit-mode` 菜单项。
- 新增 `withOfficeEditModeGuard()` 守卫函数:
- 设置 `data-mnote-last-office-edit-mode-requested="true"``data-mnote-last-office-edit-mode-guard="shown"` 以支持浏览器测试识别。
- 弹出 `window.confirm('编辑保存仍在实验中,建议先备份文件。是否继续?')` 对话框,用户确认后才继续。
- `handleTreeContextMenuAction` 中 attachment 和 isAsset 分支分别处理 `open-edit-mode` 动作,调用 guard 后再以 `mode=edit` 打开。
- Codex 修正:普通“在新窗口打开”保持 `mode=view`;只有“使用编辑模式打开”才传入 `mode=edit`
- Codex 补充 `sidebar_tree_runtime_opens_office_assets_through_resource_shell` 契约测试,覆盖默认 view、显式 edit、guard 标记和 edit URL 重写。
- `cargo test -p mnote-web sidebar_tree_runtime_opens_office_assets_through_resource_shell -- --test-threads=1` 通过。
- `cargo test -p mnote-web onlyoffice -- --test-threads=1` 通过。
- 2026-05-21Codex 浏览器复核发现并修复“已有 Office tab 切 edit 不刷新”。
- 根因:`openResourceInActiveTab()` 遇到已存在 `objectIdentity` 时只调用 `activateMainEditorTab(objectIdentity)`,没有更新旧 iframe 的 `officeUrl`
- 修复:`web_shell.rs` 新增 `refreshExistingOfficeResourceTab()`,仅对 `entry.kind === 'office'` 且新旧 `href/officeUrl` 不一致时重建 passive iframe。
- 回归:`scripts/task463-onlyoffice-resolver-smoke.js` 新增 Test 1b,断言同一 docx tab 从 `mode=view` 刷新到 `mode=edit`,且不会创建第二个 Office tab。
- Codex clean browser 自测通过:中文 docx 默认 view;同 tab edit 后 iframe URL `mode=edit`;证据见 `tmp/codex-office-view-edit-plugin-2026-05-21/result.json`
@@ -0,0 +1,87 @@
# 5-36 OnlyOffice local-folder 编辑保存 callback 契约
## 状态
- 状态:done
## 目标
为 local-folder Office 编辑保存建立 callback 写回契约。P1 可以先落设计和测试骨架;若实现,必须覆盖 status 2/6 的下载写回和路径安全。
## 原因
ONLYOFFICE 官方保存链路要求 callback `status === 2``status === 6` 时,集成后端下载 `body.url` 并写回原文件。此前 `mnote-web` callback 仍代理 legacy Nextlocal-folder 写回未闭环。本轮已补齐 local-folder callback 写回路径。
## 允许修改
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `rust/crates/adapter-onlyoffice/src/lib.rs`
- 相关单测
- 如需记录设计,可补充本 checklist
## 禁止事项
- 不绕过 allowed roots 写任意路径。
- 不在没有冲突保护时覆盖用户无关文件。
- 不强行删除 legacy callback 代理。
## Checklist
- [x] 明确 local asset callback 定位:callback URL 携带 `rootUri` / `path` / `sessionId` / `token`,并用 bridge session 校验 asset match。
- [x] status 非 2/6 时返回 `{ "error": 0 }`,不写文件。
- [x] status 2/6 时下载 rewritten `body.url`
- [x] 写回前校验目标路径属于当前 local root allowed roots。
- [x] 写回后写入原始本地文件;后续页面刷新由现有 local-folder watcher / projection refresh 感知。浏览器编辑保存端到端 smoke 另列后续验收,不作为本 bug 阻塞项。
- [x] 编辑入口仍保留 guard,不让用户误解为完整协作编辑能力已产品化。
## 验收
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1`
- 后续浏览器编辑保存验收需单独设计,不纳入 P0。
## 本轮执行记录
- 2026-06-01Codex 复核当前实现并补齐 callback 写回回归测试。
- `OnlyOfficeCallbackQuery` 已携带 `root_uri` / `path` / `session_id` / `token`
- `buildCallbackUrl()` 已把 local-folder Office asset 的 root/path/session/token 写入 callback query。
- `local_folder_onlyoffice_callback()` 已校验 root/path、bridge session token、session asset match,并通过 `resolve_onlyoffice_local_file_path()` 阻断 root escape。
- status 2/6 通过 `prepare_callback()` rewrite 后下载 `body.url` 并写回原始本地文件;非 2/6 返回成功且不写文件。
- 新增/确认测试覆盖:
- `onlyoffice_local_callback_rejects_unauthenticated_local_write`
- `onlyoffice_local_callback_writes_status_two_body_to_original_file`
- `onlyoffice_local_callback_writes_status_six_body_to_original_file`
- `onlyoffice_local_callback_rejects_root_escape_path`
- `onlyoffice_local_callback_ignores_non_write_status`
- 验证通过:
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web onlyoffice_local_callback -- --test-threads=1`
- `cargo test --manifest-path rust/Cargo.toml -p adapter-onlyoffice callback_preparation -- --test-threads=1`
- 2026-05-21Reasonix worker C 分析完成。
**结论:本轮不应实现完整写回。**
**阻塞输入缺口:**
1. callback URL 当前只携带 `assetId``userId`,缺少 `rootUri``path`
- `buildCallbackUrl()``onlyoffice.rs` 页脚 JS 中硬编码只传两个参数。
- 对于 local-folder assetassetId `local:asset:path/to/file.docx`),无法从 assetId 推导 rootUri。
- 没有 rootUri 就无法 `resolve_onlyoffice_local_file_path` → 无法安全写回。
2. `adapter-onlyoffice``prepare_callback` 不感知 local-folder 路径。
- `OnlyOfficeCallbackPreparationInput` 没有 rootUri / path 字段。
- `prepare_callback` 只做下载 URL rewrite 和 session 定位,不含路径校验。
3. callback Rust handler 的 `proxy_legacy_onlyoffice_json` 目前代理 legacy Nextlocal-folder 无 legacy 时返回 NOT_IMPLEMENTED。
**实现写回所需的最小增量(下轮实现):**
- `page()` 页脚的 `buildCallbackUrl()`:检测 local asset,追加 `rootUri``path` query 参数。
- `OnlyOfficeCallbackQuery` struct:增加 `root_uri: Option<String>``path: Option<String>`
- `callback()` handler:检测到 local asset 时(assetId 含 `local:` / `local-file:` 前缀),从 query 提取 rootUri 和 path,调用 `resolve_onlyoffice_local_file_path` 校验路径在 allowed roots 内。
- `adapter-onlyoffice`:在 `OnlyOfficeCallbackPreparationInput``prepare_callback` 增加 local path 字段。
- status 2/6:下载 rewritten body.url,写回校验过的本地路径。
- status 非 2/6:返回 `{"error": 0}`,不写文件。
**当前 guard 状态:**
- Worker B 已在 `layout.rs` 中实现 `withOfficeEditModeGuard`confirm 对话框 + `data-mnote-last-office-edit-mode-requested` / `data-mnote-last-office-edit-mode-guard` 数据属性。
- 编辑入口在 local-folder writeback 未闭环时带 guard 提示,符合安全策略。
@@ -0,0 +1,53 @@
# 5-37 OnlyOffice annotation 插件噪音策略
> 2026-06-01 复核:本文是 2026-05-21 旧口径。后续 `5-40-onlyoffice-bridge-plugin-noise-regression-v1` 引入了新的 OnlyOffice bridge/plugin 注入口径,已覆盖“当前代码无 autostart 注入”的结论。本文仅保留 annotation/custom assistant 内置插件噪音分类;主文档失败分类、`errorCode=-18`、iframe 空白和 bridge plugin 噪音治理以 `5-40` 的最终分类为准。`5-40` 已通过 `task518-onlyoffice-real-iframe-session-scope-smoke.js` 的 `errorClassification` 验收,本文不再作为活跃 process 缺陷。
## 目标
定位 OnlyOffice annotation / custom assistant 插件 404 或 pageerror 来源,并把它从主打开失败中剥离:能禁用则禁用,不能禁用则在测试和日志中明确为 non-critical。
## 原因
ONLYOFFICE 插件通过 `editorConfig.plugins.autostart``pluginsData` 注入。缺失插件资源会产生 404/pageerror,但不应影响主文档渲染、WebSocket 或只读预览。
## 允许修改
- `rust/crates/mnote-web/src/routes/onlyoffice.rs`
- `src/components/onlyoffice/` 下与自定义插件配置直接相关的文件
- 相关测试和 smoke 断言
## 禁止事项
- 不删除 OnlyOffice 主静态资源。
- 不把所有 console error 都静默吞掉。
- 不把主文档渲染失败降级为插件噪音。
## Checklist
- [x] 找到 annotation/custom assistant 插件配置或请求来源。
- [x] 若 MNote 当前不依赖该插件,禁用无效 autostart / pluginsData。(确认当前代码已无 autostart 注入,无需额外禁用)
- [x] 若属于 OnlyOffice 内置可选插件缺资源,记录为 non-critical,并更新浏览器测试过滤口径。
- [x] 补测试或文档,确保 `errorCode=-18` / WebSocket 失败仍被视为失败,不被插件噪音掩盖。(由 `5-40` / `task518-onlyoffice-real-iframe-session-scope-smoke.js``errorClassification.mainDocument` 覆盖)
## 验收
- `cargo test -p mnote-web onlyoffice -- --test-threads=1`
- Reasonix clean browser 测试的 console/network 摘要能区分插件 404 与主打开失败。
## 本轮执行记录
- 2026-05-21Reasonix worker D 分析完成。
- **来源确认**:MNote 后端代码(`onlyoffice.rs`**没有**在 `editorConfig` 中设置 `plugins.autostart``plugins.pluginsData``MNOTE_AGENT_PLUGIN_GUID` 常量虽定义但未被任何配置引用。
- 旧的 `recycle/wolai-frontend/` 中存在设置 `autostart: [MNOTE_AGENT_PLUGIN_GUID]``pluginsData: [pluginConfigUrl]` 的代码,但那是已废弃的前端实现。
- 当前 `onlyoffice.rs``page()` 函数生成的 JS 中 `editorConfig``plugins` 字段。
- `src/components/onlyoffice/onlyoffice-plugins/` 下有 OnlyOffice 内置的 annotation / custom-assistant 插件静态文件(GUID `{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}`),但这些是 OnlyOffice 服务端自带的插件发现机制,非 MNote 注入。
- **处理策略**
1. 404/pageerror 属于 OnlyOffice DocumentServer 内置可选插件自动发现的非关键链路,不影响主文档渲染。
2. 不需要禁用代码层面的 `autostart`——因为当前代码已不注入任何插件。
3. 在浏览器测试和日志分析中,将 annotation/custom-assistant 相关 404 标记为 non-critical noise**不**等同于文档打开失败。
4. 保持 `errorCode=-18`WebSocket 连接失败)和主 iframe 空白仍视为真正失败。
- 未修改代码文件。
- 2026-05-21Reasonix 与 Codex 浏览器复核一致。
- Reasonix 结果:`tmp/reasonix-office-view-edit-plugin-2026-05-21/result.json` 中插件 404 / `CustomAssistantManager is not defined` 存在,但 Office iframe 默认 view 可打开。
- Codex 结果:`tmp/codex-office-view-edit-plugin-2026-05-21/result.json` 中同样捕获 `/sdkjs-plugins/{9DC93CDB-B576-4F0C-B55E-FCC9C48DD007}/...` 404;默认 view 与显式 edit 的主 iframe 均可建立。
- 当前结论保持:该类插件错误作为 non-critical noise 记录;不应掩盖 `errorCode=-18`、主 iframe 空白、文档加载失败等真正失败。
@@ -0,0 +1,67 @@
# 5-40 ONLYOFFICE bridge 插件注入后噪音口径需复测
## 状态
- 状态:done
- Owner05-editor-mainline / OnlyOffice preview-edit / browser smoke
- 发现时间:2026-05-31
## 现象
旧缺陷 `5-37` 的结论是当前代码没有注入 `plugins.autostart` / `pluginsData`,因此 annotation/custom assistant 插件 404 可作为 OnlyOffice 内置非关键噪音处理。但近期 ONLYOFFICE live bridge 已在 `onlyoffice.rs` 中重新注入 MNote bridge 插件,这会让旧噪音策略失效,必须重新区分 MNote bridge 插件失败、OnlyOffice 内置插件噪音和真正主文档加载失败。
## 证据
- `bugs/05-editor-mainline/process/5-37-onlyoffice-annotation-plugin-noise-policy-v1.md` 记录:“当前代码已无 autostart 注入,无需额外禁用”。
- `rust/crates/mnote-web/src/routes/onlyoffice.rs` 当前 `editorConfig.plugins` 注入:
- `autostart: [MNOTE_AGENT_PLUGIN_GUID]`
- `pluginsData: [bridgePluginConfigUrl.toString()]`
## 影响
- clean browser 测试里 MNote bridge 插件加载失败可能被错误归类为“OnlyOffice 内置插件噪音”。
- 相反,主 iframe 空白、`errorCode=-18`、DocumentServer WebSocket 失败也可能被泛化过滤掉。
- Office live agent 能力上线后,插件加载失败将不再是纯噪音,而是 AI Office 工具不可用。
## 最小复现建议
1. clean browser 打开 docx/xlsx/pptx。
2. 记录 console、pageerror、network 404/500、OnlyOffice iframe ready state。
3. 分别验证:
- MNote bridge 插件 config/index 是否成功加载。
- OnlyOffice 内置 annotation/custom assistant 缺资源是否仍为 non-critical。
- 主 iframe 空白、`errorCode=-18`、WebSocket 失败仍判定为失败。
## 修复建议
- 更新 `5-37` 或新 smoke 的错误分类规则:MNote bridge 插件错误不等同于内置插件噪音。
- 在 OnlyOffice smoke 中输出插件分类字段,例如 `mnoteBridgePluginOk``builtinPluginNoiseOnly``mainDocumentReady`
- 对 bridge 插件 config/index 增加 route 单测和 browser smoke 断言。
## 本轮进展
- 2026-06-01:新增并通过 `scripts/task517-onlyoffice-bridge-plugin-direct-smoke.js`,在真实 Chromium 中 mock `Asc.plugin` 后直接加载 bridge plugin index,验证 session 注册、token 错误拒绝、`selection.get` / `document.insert_text` command 消费和 result 回传。
- 该 smoke 证明 MNote bridge plugin index / HTTP loop 自身可用,但不覆盖真实 ONLYOFFICE iframe / DocumentServer 的 autostart、内置插件噪音、主文档 ready 或 `errorCode=-18` 分类,因此本 bug 仍保留在 `process/`
- 2026-06-01:新增并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`,在真实 ONLYOFFICE iframe / DocumentServer 下验证 MNote bridge 插件可 autostart 注册并回收命令。该 smoke 记录到的 404 均为 `/api/onlyoffice/bridge/plugin/index/translations/langs.json``/api/onlyoffice/bridge/plugin/index/translations/zh-CN.json`,当前可归类为 bridge 插件翻译资源噪声;它没有阻断 session 注册、`selection.get` 或工具层授权 dry-run。
- 2026-06-01:增强并通过 `scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`,输出 `errorClassification`
- `mnoteBridgePlugin.httpErrors=[]`
- `mnoteBridgePlugin.sessionsRegistered=true`
- `mnoteBridgePlugin.commandLoopOk=true`
- `bridgeTranslationNoise.count=6`,仅包含 `/api/onlyoffice/bridge/plugin/index/translations/langs.json``zh-CN.json` 404
- `builtinPluginNoise.count=0`
- `mainDocument.ready=true`
- `mainDocument.failures=[]`
- `mainDocument.errorCodeMinus18=false`
该分类把 MNote bridge 插件失败、bridge 翻译资源噪音、OnlyOffice 内置插件噪音和主文档失败分开;`errorCode=-18` / 主 iframe 空白不会被插件噪音吞掉。
## 验收
- [x] Clean browser smoke 能区分三类错误:MNote bridge 插件失败、OnlyOffice 内置插件噪音、主文档加载失败。
- [x] `errorCode=-18` / 主 iframe 空白不会被插件噪音过滤吞掉。
- [x] bridge 插件不可用时,Office 文档预览结论和 Office AI 工具可用性结论分开报告。
## 最终验证
- `node --check scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
- `MNOTE_WEB_SMOKE_BASE_URL=http://127.0.0.1:3301 node scripts/task518-onlyoffice-real-iframe-session-scope-smoke.js`
@@ -0,0 +1,57 @@
# 5-41 Page Aggregate compat 瘦身剩余缺口
## 状态
- 状态:done
- Owner05-editor-mainline / Page Aggregate / browser document conversion
- 发现时间:2026-06-01
## 现象
Page Aggregate 已经完成 local-first 主链和多个单一真源修复。2026-06-01 复核后,`body.content`、compat join 和浏览器 legacy content 降级被明确收口为 v1 兼容面:它们仍是 active runtime 的兼容读写字段,但不再作为 local-first 浏览器主事实源。
## 证据
- `rust/crates/core-protocol/src/page_aggregate.rs``PageBody.content` 仍是必填字段。
- `rust/crates/mnote-web/src/routes/local_folder_source.rs` 的 local-first aggregate 仍会填充 `body.content`
- `rust/crates/bridge-runtime/src/lib.rs` 的 cloud/compat aggregate 路径仍支持 `CompatMetaContentJoin`
- `rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js` 已在 2026-06-01 调整为 `blockDocument/editorDocument` 优先,`local_markdown.content``compat.legacy_content` 只作为缺少 block document 时的降级路径;`scripts/task522-page-aggregate-compat-fallback-contract.js` 覆盖了这几个 source 分支。
- `rust/crates/mnote-web/src/routes/documents.rs` 的保存兼容面仍同时携带 `editorDocument``content``tiptapDocument`
- `rust/crates/mnote-web/src/page_aggregate/builder.rs` 的默认 source 已在 2026-06-01 改为 `KernelProjection`,显式 `.source(PageAggregateSource::CompatMetaContentJoin)` 仍保留 compat 路径。
## 影响
- “Page Aggregate 单一真源”仍保留 v1 兼容数据面:前端和后端都需要继续维护 legacy content,但只作为镜像 / fallback。
- local-first 浏览器转换层已优先消费 block document;后端协议和 compat 保存面继续保留 legacy content,保证 cloud/compat fallback 和旧保存面不被破坏。
- 后续若要删除或改为可选 `body.content`,必须进入 `mnote.page_aggregate.v2` 或等价协议升级 checklist,不能在 v1 中直接删除。
## 下一步建议
- `PageBody.content``mnote.page_aggregate.v1` 中继续保持必填兼容字段,用于 legacy/cloud/compat fallback、旧保存面和降级回读。
- `body.blockDocument` / `editorDocument` 是 local-first 浏览器主消费面;`body.content` 不再作为 active editor 初始化的优先事实源。
- `CompatMetaContentJoin` 继续只作为显式 cloud/compat substrate 边界保留;`PageAggregateBuilder::new()` 默认 source 保持 `KernelProjection`
- `body.content` 改为可选或删除的退出条件:必须先有 `mnote.page_aggregate.v2` 协议或迁移 checklist,证明 cloud/compat 保存面、外部 agent、历史 fixture 和降级读取全部不再依赖该字段。
## 验收
- [x] local-first 浏览器转换优先消费 `body.blockDocument``pageBodyTiptapDocumentSource(...)``blockDocument` 存在时返回 `page_aggregate.block_document`
- [x] `body.content` 仅在缺少 `blockDocument` / `editorDocument` 时作为 local markdown legacy fallback。
- [x] Builder 默认 source 不再是 `CompatMetaContentJoin`
- [x] `compat.legacy_content` 降级路径有明确 source 标记和测试覆盖。
- [x] 设计稿明确 `body.content` 的长期状态:v1 保留为必填兼容镜像 / fallback;v2 或后续协议升级再评估可选或删除。
## 2026-06-01 验证
- `node --check rust/crates/mnote-web/browser/document-tiptap-conversion-runtime.js`
- `node --check scripts/task522-page-aggregate-compat-fallback-contract.js`
- `node scripts/task522-page-aggregate-compat-fallback-contract.js`
- `cargo test -p mnote-web document_shell_returns_page_aggregate_snapshot -- --test-threads=1`
- `cargo test -p mnote-web page_aggregate::builder::tests -- --test-threads=1`
- `MNOTE_UI_BASE_URL=http://127.0.0.1:3301 node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js`
## 2026-06-01 收口决策
- `PageBody.content`:v1 保留必填,语义降级为兼容镜像 / fallback,不作为 local-first active editor 主事实源。
- `body.blockDocument` / `editorDocument`local-first 浏览器转换优先消费的 canonical block view。
- `compat.legacy_content`:仅当缺少 `blockDocument` / `editorDocument` 时可被浏览器转换层使用,并通过 `task522-page-aggregate-compat-fallback-contract.js` 固化 source 标记。
- `CompatMetaContentJoin`:显式 compat substrate 边界,保留到 cloud/legacy 保存面完成协议升级,不再是 builder 默认 source。