feat: align local-first workspace direction
Document the VSCode-like local-first product shape, demote Convex to a control-plane role, and retire stale architecture drafts. Add local workspace migration/export references plus smoke coverage for no-Convex managed workspace startup, local markdown title/body/options persistence, asset upload behavior, and Convex fixture export. Verification: git diff --cached --check; node scripts/check-local-first-convex-guard.js --staged; node scripts/task444-convex-workspace-export-local-fixture-smoke.js; node scripts/task166-local-first-managed-workspace-no-convex-smoke.js; node scripts/task167-local-markdown-title-body-options-no-convex-smoke.js
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
# 3-16 [done] 本地 Markdown 上传误走云端 media asset 修复 v1
|
||||
|
||||
> 创建时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 所属主线:`03-rust-web` / `local_folder` / `local markdown`
|
||||
|
||||
## 问题
|
||||
|
||||
本地文件夹模式下,`.md` 页面内上传图片或附件沿用了云端 `/api/media/upload` 链路,会生成 Convex media asset,并在正文中写入云端 URL 或 OnlyOffice assetId 链接。
|
||||
|
||||
这会破坏本地 Markdown 的可迁移性:离开 MNote 后文件引用不可读,也无法符合 local folder “文件系统即资源真源”的边界。
|
||||
|
||||
## 修复
|
||||
|
||||
- 新增 `POST /api/local-folder/assets/upload`。
|
||||
- 本地上传复制到当前 Markdown 同目录下的 `{mdBase}.assets/`。
|
||||
- 返回给编辑器的 `sourcePath` / `file_url` 使用相对当前 Markdown 目录的路径。
|
||||
- 前端在 `sourceKind=local_folder` 时分流到本地上传入口,云端页面继续走 `/api/media/upload`。
|
||||
- 本地附件插入普通相对路径链接,不再生成 OnlyOffice assetId URL。
|
||||
- 本地图片保存时写回标准 Markdown 图片语法:``。
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
cargo fmt --manifest-path rust/Cargo.toml --all --check
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown_ -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_folder -- --nocapture
|
||||
```
|
||||
|
||||
临时 HTTP smoke:
|
||||
|
||||
```bash
|
||||
MNOTE_WEB_BIND=127.0.0.1:13001 MNOTE_WEB_PUBLIC_BIND=127.0.0.1:13001 cargo run --manifest-path rust/Cargo.toml -p mnote-web --bin mnote-web
|
||||
curl -sS -X POST \
|
||||
-F "file=@/etc/hosts;filename=photo.png;type=image/png" \
|
||||
-F "rootUri=file:///tmp/mnote-local-smoke-..." \
|
||||
-F "documentId=local-md:README.md" \
|
||||
-F "kind=image" \
|
||||
http://127.0.0.1:13001/api/local-folder/assets/upload
|
||||
```
|
||||
|
||||
结果:
|
||||
|
||||
- `local_markdown_`:19 passed
|
||||
- `local_folder`:36 passed
|
||||
- `cargo fmt --check`:通过
|
||||
- HTTP smoke:返回 `sourcePath=README.assets/photo-1.png`,落盘文件存在且 size=188
|
||||
|
||||
## 剩余
|
||||
|
||||
- 尚未跑真实浏览器 smoke:本地 `.md` 页面上传图片/附件、刷新后恢复、文件树可见。
|
||||
@@ -0,0 +1,47 @@
|
||||
# 5-19 [done] Page Aggregate 块投影未优先消费 EditorBlockDocument
|
||||
|
||||
## 级别
|
||||
|
||||
P1 / 05-editor-mainline 主线迁移缺陷。
|
||||
|
||||
## 现象
|
||||
|
||||
`page.body.save` 已在 Rust runtime plan 与 artifact 中保留 `editorDocument`,但 `page.aggregate.get` 构建 `body.blockDocument` 时仍固定从 legacy `documents.content` 投影。
|
||||
|
||||
当 `editorDocument` 与旧 `content` 不一致时,AI 块读取、块定位与 Page Aggregate 回读会继续看到旧内容,导致页面域单一真源迁移停留在 legacy content 过渡层。
|
||||
|
||||
## 根因
|
||||
|
||||
`rust/crates/bridge-runtime/src/lib.rs` 的 `build_page_aggregate_projection_result` 只读取 `content_result.content`,随后调用 `project_legacy_content_to_block_document`,并把 `projectionSource` 固定为 `documents.content`。
|
||||
|
||||
## 修复
|
||||
|
||||
- `page.aggregate.get` 的内容结果识别现在包含 `editorDocument` 与 `blockDocument`。
|
||||
- Page Aggregate `body.blockDocument` 来源顺序调整为:
|
||||
- `editorDocument`:优先解析 `EditorBlockDocument`,由 Rust runtime 投影为 Page Aggregate 块文档。
|
||||
- `blockDocument`:已有块投影直接作为次级来源。
|
||||
- `documents.content`:仅在缺少原生块文档来源时回退。
|
||||
- `body.projectionSource` 改为真实来源:`editorDocument` / `blockDocument` / `documents.content`。
|
||||
- 新增回归测试 `page_aggregate_get_prefers_editor_document_over_legacy_content`,覆盖 `editorDocument` 与旧 `content` 冲突时的优先级。
|
||||
- 新增非法原生块文档防回退保护:
|
||||
- `editorDocument` 存在但不可解析时返回 validation error,不再静默回退到旧 `content`。
|
||||
- `blockDocument` 存在但不是对象时返回 validation error,不再静默回退到旧 `content`。
|
||||
- Page Aggregate `body.content` 也同步按原生来源重建:
|
||||
- `editorDocument` 来源下由 `EditorBlockDocument` 生成 legacy content。
|
||||
- `blockDocument` 来源下由块投影生成 legacy content。
|
||||
- 避免同一份 Page Aggregate 中 `body.blockDocument` 正确但 `body.content` 仍指向旧正文,导致编辑器/session 下游继续显示或保存旧真相。
|
||||
|
||||
## 验证
|
||||
|
||||
- RED:`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate_get_prefers_editor_document_over_legacy_content -- --nocapture`
|
||||
- 旧实现失败,返回 `legacy_1` 而不是 `editor_1`。
|
||||
- GREEN:`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime page_aggregate -- --nocapture`
|
||||
- 6 passed。
|
||||
- Rust Web 回归:`cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate -- --nocapture`
|
||||
- 7 passed。
|
||||
- 格式:`cargo fmt --manifest-path rust/Cargo.toml --all -- --check`
|
||||
- passed。
|
||||
|
||||
## 剩余边界
|
||||
|
||||
这次修的是 Page Aggregate 读侧优先级,不是 Convex 原生 `EditorBlockDocument` 落库 schema 的最终切换。`documents:updateContent` 兼容写入仍保留 legacy content substrate;后续主线应继续推进持久层原生块文档字段与命令写入闭环。
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
# 5-20 [done] page.body.save 发往 Convex 前剥离 EditorBlockDocument
|
||||
|
||||
## 级别
|
||||
|
||||
P1 / 05-editor-mainline 主线迁移缺陷。
|
||||
|
||||
## 现象
|
||||
|
||||
`page.body.save` 的 Rust runtime plan 已经生成 `editorDocument` / `tiptapDocument`,但 `mnote-web` transport 在真正调用 `documents:updateContent` 前会删除这两个字段。
|
||||
|
||||
这会导致原生块文档只存在于 plan / artifact 里,持久层仍只能保存 legacy `content`。刷新后 Page Aggregate 即使读侧已支持 `editorDocument -> blockDocument -> documents.content`,也会因为 substrate 没有原生字段而回到 `documents.content`。
|
||||
|
||||
## 根因
|
||||
|
||||
`rust/crates/mnote-web/src/transport/convex.rs` 将 `documents.save | page.body.save` 视为 legacy validator,只保留 `id/content/expectedRevision/conflictDetectionKey`,显式删除 `editorDocument` 和 `tiptapDocument`。
|
||||
|
||||
同时,当前可见的 Convex `documents:updateContent` 源码仍只声明 `content`,没有持久化原生编辑器快照字段。
|
||||
|
||||
## 修复
|
||||
|
||||
- `mnote-web` transport 不再剥离 `editorDocument` / `tiptapDocument`,只剥离 Rust artifact 事件字段。
|
||||
- 本地可见 Convex substrate `recycle/wolai-frontend/convex/documents.ts` 已同步接受并写入:
|
||||
- `editorDocument -> documents.editor_document`
|
||||
- `tiptapDocument -> documents.tiptap_document`
|
||||
- `getContent` / `getContentForIngest` 已同步返回 `editorDocument` / `tiptapDocument`,让 Page Aggregate 读侧能够优先消费原生块文档。
|
||||
- `recycle/wolai-frontend/convex/schema.ts` 已补 `editor_document` / `tiptap_document` optional 字段。
|
||||
|
||||
说明:`recycle/` 当前被仓库 `.gitignore` 忽略,但这是本仓库可见的唯一 `documents:updateContent` Convex 源码位置;tracked Rust transport 已按该 validator 口径更新。
|
||||
|
||||
## 验证
|
||||
|
||||
- RED:`cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_keeps_editor_runtime_fields_for_page_body_save -- --nocapture`
|
||||
- 旧实现失败,实际 args 中 `editorDocument` / `tiptapDocument` 被删除。
|
||||
- GREEN:`cargo test --manifest-path rust/Cargo.toml -p mnote-web convex_command_args_keeps_editor_runtime_fields_for_page_body_save -- --nocapture`
|
||||
- 1 passed。
|
||||
|
||||
## 剩余边界
|
||||
|
||||
这次修复让 `page.body.save` 不再在 Rust Web transport 边界丢弃原生块文档,但完整 EditorBlockDocument 原生落库仍需要后续真实 Convex 部署 schema 与迁移流程确认。历史页面已有 legacy `content`,不会自动反推补齐 `editor_document`。
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
# 5-21 [done] page.body.save 对非法 editorDocument 不能静默回退
|
||||
|
||||
## 级别
|
||||
|
||||
P1 / 05-editor-mainline 主线迁移缺陷。
|
||||
|
||||
## 现象
|
||||
|
||||
`page.body.save` 在保存时会同时携带 `editorDocument`、`tiptapDocument` 和 legacy `content`。
|
||||
当 `editorDocument` 字段存在但结构非法时,原实现会静默回退到 `tiptapDocument` 或 `content`,把原生块快照损坏隐藏掉。
|
||||
|
||||
## 根因
|
||||
|
||||
`normalize_save_editor_document(...)` 之前只在 `serde_json::from_value::<EditorBlockDocument>` 成功时才使用 `editorDocument`,失败后会继续尝试 `tiptapDocument`,最后再回退 `legacy content`。
|
||||
|
||||
## 修复
|
||||
|
||||
- `editorDocument` 一旦存在就必须可解析。
|
||||
- `editorDocument` 无法反序列化时,直接返回 validation error,不再回退到 `tiptapDocument` 或 `content`。
|
||||
- 现有“合法 editorDocument 优先于其他来源”语义保持不变。
|
||||
|
||||
## 验证
|
||||
|
||||
- RED:`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime documents_save_command_plan_rejects_unparseable_editor_document_instead_of_falling_back -- --nocapture`
|
||||
- 旧实现失败,证明原先会静默回退。
|
||||
- GREEN:`cargo test --manifest-path rust/Cargo.toml -p bridge-runtime documents_save_command_plan -- --nocapture`
|
||||
- 12 passed。
|
||||
|
||||
## 剩余边界
|
||||
|
||||
这次修复只收紧非法 `editorDocument` 的失败语义,不改变合法 `editorDocument` / `tiptapDocument` 的既有优先级顺序。
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
# 5-22 [done][bug] documents.content.get 在 Page Aggregate 前剥离原生块文档字段 v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`05-editor-mainline`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
`page.aggregate.get` 已支持按 `editorDocument -> blockDocument -> documents.content` 的顺序构建正文真相,但 `/api/page-aggregate/:id` 主链会先通过 `documents.content.get` 读取内容。
|
||||
|
||||
旧的 `DocumentContentResult` 只保留:
|
||||
|
||||
- `content`
|
||||
- `revision`
|
||||
- `conflictDetectionKey`
|
||||
- `title`
|
||||
- `pageSubtree`
|
||||
|
||||
上游返回的 `editorDocument / blockDocument / tiptapDocument` 被提前剥离,导致 Page Aggregate route 真实运行时仍回落到 `documents.content`。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`bridge-runtime` 的 `build_document_content_result` 没有把原生块文档字段带入 `DocumentContentResult`。
|
||||
|
||||
这使 5-19 的 Page Aggregate 优先级修复只在合成输入测试里成立,真实 `mnote-web` route 入口仍拿不到原生字段。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- `core-protocol::DocumentContentResult` 新增可选字段:
|
||||
- `editorDocument`
|
||||
- `tiptapDocument`
|
||||
- `blockDocument`
|
||||
- `blockProjectionVersion`
|
||||
- `build_document_content_result` 保留这些上游字段。
|
||||
- `mnote-web` Page Aggregate route 回归测试加入含 `editorDocument` 的 `documents:getContent` fixture,并断言:
|
||||
- `body.projectionSource=editorDocument`
|
||||
- `body.blockDocument.rootBlockIds[0]=editor_1`
|
||||
- `body.blockDocument.blocks[0].text` 来自 `editorDocument`
|
||||
|
||||
## 4. 验证
|
||||
|
||||
RED:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_returns_snapshot_contract -- --nocapture
|
||||
```
|
||||
|
||||
旧实现失败,`projectionSource` 仍为 `documents.content`。
|
||||
|
||||
GREEN:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_aggregate_endpoint_returns_snapshot_contract -- --nocapture
|
||||
```
|
||||
|
||||
结果:`1 passed`。
|
||||
|
||||
## 5. 剩余边界
|
||||
|
||||
这修复的是 Rust route 读取链字段保留;底层 Convex schema/mutation 是否已完整原生落库仍按后续持久层迁移继续跟进。
|
||||
@@ -0,0 +1,59 @@
|
||||
# 5-23 [done][bug] editorDocument 缺少 blocks 或 root 引用损坏仍被当作合法真源 v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`05-editor-mainline`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
`EditorBlockDocument` 的 serde 结构对 `blocks/rootBlockIds` 使用默认值。旧实现只检查 JSON 是否能反序列化,导致这类输入被接受:
|
||||
|
||||
```json
|
||||
{"documentId":"doc_1"}
|
||||
```
|
||||
|
||||
或者:
|
||||
|
||||
```json
|
||||
{"documentId":"doc_1","rootBlockIds":["missing"],"blocks":[]}
|
||||
```
|
||||
|
||||
它们会优先于 legacy content 被当作原生块文档真相,可能把页面保存为空,或让 `body.content` 与 `body.blockDocument` 分裂。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`normalize_save_editor_document` 与 `page_aggregate_block_document_projection` 只做 serde 形状解析,没有做原始字段存在性和 root 引用完整性校验。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- 新增 `validate_editor_document_structure`。
|
||||
- 显式 `editorDocument` 必须带 `blocks` 数组。
|
||||
- `rootBlockIds` 中的非空 id 必须能在 `blocks` 中找到。
|
||||
- 保存链和 Page Aggregate 读链共用同一校验。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
RED:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime rejects_editor_document -- --nocapture
|
||||
```
|
||||
|
||||
旧实现中以下两个新增测试失败:
|
||||
|
||||
- `documents_save_command_plan_rejects_editor_document_missing_blocks`
|
||||
- `page_aggregate_get_rejects_editor_document_root_ids_missing_blocks`
|
||||
|
||||
GREEN:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime rejects_editor_document -- --nocapture
|
||||
```
|
||||
|
||||
结果:`2 passed`。
|
||||
|
||||
## 5. 剩余边界
|
||||
|
||||
当前校验保证 `editorDocument` 不会因 serde default 被误判为合法真源;更深的 schema 版本、块类型特定必填字段仍应在 EditorBlockDocument 原生落库迁移中继续收紧。
|
||||
+60
@@ -0,0 +1,60 @@
|
||||
# 5-24 [done][bug] page.body.save 上传图片与附件持久化字段丢失 v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`05-editor-mainline`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
主编辑区文本保存修复后,继续验证上传图片与附件发现两个持久化缺陷:
|
||||
|
||||
- 附件能插入到编辑器,并且 `/api/documents/save` 返回 200,但 `/api/documents/content` 回读时链接 mark 被降级成纯文本。
|
||||
- 图片能上传并触发保存,但 `/api/documents/content` 回读时图片块只剩 `type: "image"`,`props.src / alt / title / tiptapImage` 为空。
|
||||
|
||||
这会导致刷新或跨浏览器打开后,附件不能作为 OnlyOffice 附件链接恢复,图片也缺少可渲染的资源地址。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
保存链优先使用 `editorDocument` 生成 canonical legacy content 后再写入当前 Convex legacy substrate。
|
||||
|
||||
两个字段在这一步丢失:
|
||||
|
||||
- `legacy_content_from_editor_document` 只用 `legacy_text_from_editor_block()` 生成纯文本,未保留 `ContentNode.attrs.styles.link`。
|
||||
- `EditorBlockDocument` 反序列化时 `BlockProps.extra` 不是 flatten 字段,前端传来的 image `props.src / alt / title / tiptapImage` 会被 serde 忽略;`hydrate_editor_document_props_from_raw` 旧实现只回填 mindmap props,没有回填 image props。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- `legacy_content_from_editor_document` 对带结构化样式的 inline 节点输出 legacy inline content 数组,保留 `styles.link` 等样式;普通纯文本段落仍保持旧的字符串 content。
|
||||
- `hydrate_editor_document_props_from_raw` 增加 image 分支,从原始 `editorDocument.blocks[].props` 回填 `src / alt / title / tiptapImage`。
|
||||
- `legacy_props_from_editor_block` 对 image 块输出回填后的直接 props 与 `tiptapImage`。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
单测:
|
||||
|
||||
```bash
|
||||
cargo fmt --manifest-path rust/Cargo.toml --all --check
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime legacy_content_from_editor_document_preserves_inline_link_styles -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime documents_save_hydrates_image_props_from_raw_editor_document -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p bridge-runtime documents_save_command_plan_prefers_valid_editor_document_over_other_sources -- --nocapture
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web documents_save_route_executes_page_body_save_command -- --nocapture
|
||||
```
|
||||
|
||||
浏览器验证:
|
||||
|
||||
- 以测试账号登录 `http://127.0.0.1:13000`。
|
||||
- 新建临时页面。
|
||||
- 通过 slash 上传 `.docx` 附件和 `.png` 图片。
|
||||
- 断言两次 `/api/documents/save` 均返回 200。
|
||||
- 断言 `/api/documents/content` 回读包含:
|
||||
- image `props.src`
|
||||
- 附件 inline `styles.link`
|
||||
- 刷新页面后,编辑器 DOM 中仍能找到附件链接与图片节点。
|
||||
|
||||
结果:通过;临时测试页面已 purge。
|
||||
|
||||
## 5. 剩余边界
|
||||
|
||||
当前仍是 legacy content substrate 下的过渡修复。完整 EditorBlockDocument 原生落库完成后,图片/附件 props 应由原生 schema 和迁移校验继续收紧。
|
||||
@@ -0,0 +1,64 @@
|
||||
# 7-31 [done][bug] 页面 AI block_edit_workflow 丢弃读取类回答摘要 v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`07-ai`
|
||||
|
||||
## 1. 用户可见症状
|
||||
|
||||
在页面 AI 使用 `ACP · Reasonix` 输入:
|
||||
|
||||
```text
|
||||
检查你是否能读取到本页第一段,同时请修改第二段为:测试123
|
||||
```
|
||||
|
||||
`block_edit_workflow` 即使完成写入,前端也只显示固定文案:
|
||||
|
||||
```text
|
||||
已通过页面 markdown 编辑快路径完成写入。
|
||||
```
|
||||
|
||||
用户要求中的“是否能读取到第一段”没有被回答。
|
||||
|
||||
## 2. 根因
|
||||
|
||||
`page_ai_workflow.rs` 的模型提示已经要求模型输出:
|
||||
|
||||
```json
|
||||
{"operations": [...], "summary": "..."}
|
||||
```
|
||||
|
||||
但路由只提取 `operations`,丢弃了 `summary`,并在成功响应中固定返回“已通过页面 markdown 编辑快路径完成写入。”。
|
||||
|
||||
这会让读写混合请求退化为纯写入反馈。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- 新增 `MarkdownEditPlan`,同时解析模型输出中的 `operations` 与 `summary`。
|
||||
- `block_edit_workflow` 成功响应优先返回模型 `summary`,缺失时才使用固定 fallback。
|
||||
- 系统提示明确要求:如果用户要求读取某段,`summary` 必须包含从 `page_text` 读取到的原文。
|
||||
- 新增回归测试 `block_edit_workflow_surfaces_model_summary_for_read_and_edit_request`,覆盖“读取第一段 + 修改第二段”的真实快路径响应。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
RED:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web block_edit_workflow_surfaces_model_summary_for_read_and_edit_request -- --nocapture
|
||||
```
|
||||
|
||||
旧实现失败,`payload.message` 不包含 `已读取第一段:第一段`。
|
||||
|
||||
GREEN:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_workflow -- --nocapture
|
||||
```
|
||||
|
||||
结果:`5 passed`。
|
||||
|
||||
## 5. 剩余边界
|
||||
|
||||
本修复只保证 fast workflow 成功响应不丢模型摘要;复杂多步推理、review session、流式 apply 仍属于 Phase C 冻结范围。
|
||||
@@ -0,0 +1,50 @@
|
||||
# 7-32 [done][bug] markdown_edit 直接 page.body.save 时丢失 revision / conflictDetectionKey v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`07-ai`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
7-27 后,在线 `mnote.doc.markdown_edit` 不再通过空 `block_ops` 写回,而是以最终 Markdown 生成 `content` 后直接执行 `page.body.save`。
|
||||
|
||||
旧实现构造的写入 payload 只包含:
|
||||
|
||||
- `documentId`
|
||||
- `workspaceId`
|
||||
- `content`
|
||||
- `mode`
|
||||
|
||||
没有携带当前 Page Aggregate 的 `revision` 与 `conflictDetectionKey`。
|
||||
|
||||
## 2. 影响
|
||||
|
||||
AI markdown 主路径可能基于旧快照覆盖用户并发编辑,绕过 7-22 已补到 `apply_block_ops` 的写前置条件。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- 在线 `markdown_edit` 写回前读取当前 Page Aggregate。
|
||||
- 构造 `page.body.save` payload 时带入:
|
||||
- `/body/revision`
|
||||
- `/body/conflictDetectionKey`
|
||||
- 新增回归测试检查 command artifact payload 中真实携带这两个字段。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
RED:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_online_page_body_save_carries_revision_conflict_key -- --nocapture
|
||||
```
|
||||
|
||||
旧实现失败,artifact payload 中 `revision` 为 `Null`。
|
||||
|
||||
GREEN:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_online_page_body_save_carries_revision_conflict_key -- --nocapture
|
||||
```
|
||||
|
||||
结果:`1 passed`。
|
||||
@@ -0,0 +1,41 @@
|
||||
# 7-33 [done][bug] markdown_edit 全部 search 失败仍返回成功 v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`07-ai`
|
||||
|
||||
## 1. 问题定义
|
||||
|
||||
`mnote.doc.markdown_edit` 逐条执行 search/replace 时,如果所有 search 都失败,旧实现仍会继续进入写回阶段,并返回:
|
||||
|
||||
```json
|
||||
{"ok": true, "operationsApplied": 0}
|
||||
```
|
||||
|
||||
这会让页面 AI fast workflow 把“没有改动”误报为编辑成功。
|
||||
|
||||
## 2. 修复
|
||||
|
||||
- 当 `applied == 0` 时立即返回 `mnote_markdown_edit_no_operations_applied`。
|
||||
- 不再执行本地文件写入或在线 `page.body.save`。
|
||||
- 新增回归测试 `hermes_tools_markdown_edit_rejects_no_applied_operations`。
|
||||
|
||||
## 3. 验证
|
||||
|
||||
RED:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_rejects_no_applied_operations -- --nocapture
|
||||
```
|
||||
|
||||
旧实现返回 `200`。
|
||||
|
||||
GREEN:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web hermes_tools_markdown_edit_rejects_no_applied_operations -- --nocapture
|
||||
```
|
||||
|
||||
结果:`1 passed`。
|
||||
@@ -0,0 +1,45 @@
|
||||
# 7-34 [done][bug] 页面 AI ACP Runtime 默认值回跳 Hermes v1
|
||||
|
||||
> 发现时间:2026-05-18
|
||||
>
|
||||
> 状态:`[done]`
|
||||
>
|
||||
> 关联主线:`07-ai`
|
||||
|
||||
## 1. 用户可见症状
|
||||
|
||||
页面 AI 面板中选择 `ACP · Reasonix` 后,会自动回跳到 `ACP · Hermes`。
|
||||
|
||||
用户期望:
|
||||
|
||||
- 默认 runtime 为 `ACP · Reasonix`
|
||||
- 下拉仍可手动切换到 `ACP · Hermes`
|
||||
|
||||
## 2. 根因
|
||||
|
||||
页面 AI 的历史 runtime 语义仍把空 `acpRuntime` 当成旧 `Hermes HTTP` 默认值。当前主线已经退役 HTTP Hermes,但前端 session/localStorage 恢复、后端空 runtime 推导、以及 `/profiles` 尚未返回时的下拉渲染仍可能产生空 runtime。
|
||||
|
||||
空 runtime 进入旧逻辑后会被解释成 Hermes 路径,导致用户刚选择 Reasonix 后又被历史 session 或空 select value 覆盖。
|
||||
|
||||
## 3. 修复
|
||||
|
||||
- 前端 `pageAiAcpRuntime` 初始值改为 `reasonix`。
|
||||
- localStorage 版本提升到 `3`,避免旧缓存把 active runtime 覆盖回 Hermes。
|
||||
- session 创建、持久化、停止 run、发送 run 默认都写入 `reasonix`。
|
||||
- 前端内置 `Reasonix / Hermes` 两个 ACP runtime fallback,`/profiles` 慢或失败时也不会渲染空 select。
|
||||
- 后端空 `acpRuntime` 默认归入 ACP 默认 runtime,默认值为 `reasonix`;旧 HTTP proxy 只保留在显式兼容开关后。
|
||||
|
||||
## 4. 验证
|
||||
|
||||
新增回归约束:
|
||||
|
||||
```bash
|
||||
cargo test --manifest-path rust/Cargo.toml -p mnote-web page_ai_acp_runtime_defaults_to_reasonix_and_keeps_hermes_switch -- --nocapture
|
||||
```
|
||||
|
||||
覆盖:
|
||||
|
||||
- `pageAiAcpRuntime` 默认是 `reasonix`
|
||||
- 下拉 options 固定包含 `reasonix` / `hermes`
|
||||
- 不再包含旧的 `默认 (Hermes HTTP)` 空选项
|
||||
- change handler 空值 fallback 到 `reasonix`
|
||||
@@ -59,6 +59,11 @@
|
||||
- `bugs/05-editor-mainline/done/5-16-sidebar-preferred-snapshot-query-overrides-live-stream-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-17-document-title-source-drift-live-sidebar-over-head-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-18-ai-write-body-does-not-sync-page-aggregate-content-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-19-page-aggregate-prefers-editor-document-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-20-page-body-save-strips-editor-document-before-convex-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-21-page-body-save-rejects-invalid-editor-document-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-22-documents-content-strips-editor-document-before-page-aggregate-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-23-editor-document-semantic-validation-v1.md`
|
||||
- `bugs/05-editor-mainline/done/5-9-c12-topbar-sidebar-toggle-noop-v1.md`
|
||||
|
||||
收口结果:
|
||||
@@ -66,6 +71,8 @@
|
||||
- Page Aggregate 标题、正文、页面设置、page tree、AI fetch 回读已经形成同一组可验证真相。
|
||||
- AI 外部写入后,文档 shell 会同步 Page Aggregate JSON script / session snapshot,避免下一次 AI context 继续读旧正文。
|
||||
- Sidebar preferred snapshot 与 live stream 的优先级漂移已从当前 P0 blocker 中移除。
|
||||
- Page Aggregate 读侧已从 route 主链保留 `editorDocument / blockDocument`,不再在 `documents.content.get` 结果中提前剥离原生块文档字段。
|
||||
- `editorDocument` 显式输入已补语义校验:必须带 `blocks`,且 `rootBlockIds` 必须引用真实块。
|
||||
|
||||
关键证据:
|
||||
|
||||
@@ -92,6 +99,9 @@
|
||||
- `bugs/07-ai/done/7-28-markdown-edit-selection-scope-not-enforced-v1.md`
|
||||
- `bugs/07-ai/done/7-29-markdown-edit-flattens-inline-content-v1.md`
|
||||
- `bugs/07-ai/done/7-30-block-tools-selection-scope-not-enforced-v1.md`
|
||||
- `bugs/07-ai/done/7-31-page-ai-workflow-drops-read-summary-v1.md`
|
||||
- `bugs/07-ai/done/7-32-markdown-edit-page-body-save-missing-preconditions-v1.md`
|
||||
- `bugs/07-ai/done/7-33-markdown-edit-no-applied-operations-success-v1.md`
|
||||
|
||||
收口结果:
|
||||
|
||||
@@ -105,6 +115,9 @@
|
||||
- `mnote.doc.markdown_edit` / `page_ai_workflow` 已补 selection scope 服务端兜底,`allowedTargetBlockIds` 外的写入会被拒绝。
|
||||
- `mnote.doc.markdown_edit` 写回普通块时不再无条件扁平化 inline content;未变更块保持原结构,单 inline text 节点的已变更块保留 marks / attrs。
|
||||
- 单个 `mnote.block.*` 写工具已补 selection scope 服务端兜底,越界块写入会返回 `mnote_block_target_out_of_scope`。
|
||||
- 页面 AI `block_edit_workflow` 现在会把模型 `summary` 返回给前端;“读取第一段 + 修改第二段”这类读写混合请求不再被固定写入文案吞掉读取结果。
|
||||
- 在线 `mnote.doc.markdown_edit` 直接 `page.body.save` 写回时已携带当前 `revision/conflictDetectionKey`。
|
||||
- `mnote.doc.markdown_edit` 全部 search 失败时返回 `mnote_markdown_edit_no_operations_applied`,不再误报成功或触发无意义保存。
|
||||
|
||||
关键证据:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user