# 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`
