收口 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:
@@ -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`
|
||||
- P0:OnlyOffice 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`
|
||||
- P1:local-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-21:Reasonix 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-21:Codex 复核补充。
|
||||
- 干净浏览器上下文上传并打开 `/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-01:Codex 补齐 `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-21:Reasonix 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-21:Codex 浏览器复核发现并修复“已有 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 Next,local-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-01:Codex 复核当前实现并补齐 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-21:Reasonix worker C 分析完成。
|
||||
**结论:本轮不应实现完整写回。**
|
||||
|
||||
**阻塞输入缺口:**
|
||||
|
||||
1. callback URL 当前只携带 `assetId` 和 `userId`,缺少 `rootUri` 和 `path`。
|
||||
- `buildCallbackUrl()` 在 `onlyoffice.rs` 页脚 JS 中硬编码只传两个参数。
|
||||
- 对于 local-folder asset(assetId `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 Next;local-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-21:Reasonix 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-21:Reasonix 与 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
|
||||
- Owner:05-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
|
||||
- Owner:05-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。
|
||||
Reference in New Issue
Block a user