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