Files
mnote/design/05-editor-mainline/process/5-27-local-markdown-working-copy-conflict-contract-v1.md
T

68 lines
4.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 5-27 本地 Markdown Working Copy 冲突合同 v1
## 背景
`bugs/0524.md` 第 2 条暴露的问题不是单个上传入口错误,而是本地文件夹 watcher、正文 session、文件树资源事件和冲突 UI 之间缺少清晰边界:
- 上传附件或向文件树拖入文件后,非 Markdown 文件变化会进入正文外部变更链路。
- 新建页面后,旧的冲突提示可能被同 root 的事件流带到新页面,刷新后消失。
- 真实冲突仍然需要保留:当前 Markdown 有未保存编辑,同时磁盘上的同一个 Markdown 文件被外部修改时,必须进入冲突处理。
## Sidex 对照
Sidex/VSCode 的核心模型是 `StoredFileWorkingCopy`
- 每个 working copy 绑定一个具体 `resource`
- 保存时以 `lastResolvedFileStat.etag/mtime` 做 dirty write prevention。
- 只有同一个 resource 的写入出现 `FILE_MODIFIED_SINCE` 时,才进入 `inConflictMode`
- 文件系统 watcher 的目录级事件不会直接把同目录其它文件变化升级成当前 working copy 冲突。
MNote 不需要照搬 VSCode 的实现,但应采用同一条合同:正文冲突只属于当前 Markdown 文件,不属于同 root 下任意附件、文件树资源或元数据文件。
## 当前根因
当前 `mnote-web` 的 document event channel 以 `rootUri` 共享:
- 前端订阅 `/api/local-folder/events?rootUri=...`,没有传 `documentId`
- 后端 `build_document_events_stream` 在没有 `documentId` 时不会按 Markdown 相对路径过滤。
- watcher 对非 Markdown 资源发出的 payload 中 `documentId` 为空。
- 前端收到空 `documentId` 后,仍会对同 root 下所有 document session 设置 `externalChangePending` 并调度正文刷新。
这会把附件上传、文件树拖入、资源创建等 root 级变化误投递到正文 session,形成文件冲突误报和跨页面状态污染。
另一个独立根因是冲突 UI 的 DOM 生命周期:
- `renderSessionConflictSurface` 会把冲突面板直接插入 `.document-pane`
- 页面切换时 `unmountEditorViewBinding` 只卸载 editor runtime 和事件监听,没有清理旧 session 的冲突面板。
- 因此旧页面已出现冲突时,新建/切换到新页面可能短暂看到旧冲突面板;刷新后整页 DOM 重建,问题消失。
## 合同
1. Markdown 正文 session 只订阅自己的 Markdown 文件事件。
2. 非 Markdown 资源事件只用于文件树/资源投影刷新,不触发正文 `externalChangePending`
3. 真实正文冲突只在“同一个 Markdown documentId + 当前 session dirty/saving/saveTimer/recent input”时出现。
4. 同 root 下不同 Markdown 页面必须有独立 document event channel。
5. 文件树 live stream 继续承担 root 级变化刷新,不依赖正文冲突链路。
## 已执行修复切片
- 前端 document event channel key 从 `rootUri` 收窄为 `rootUri#documentId`
- 前端订阅本地文件夹 document events 时携带 `documentId`
- 前端收到无 `documentId` 的 change payload 时直接忽略,不再设置正文 `externalChangePending`
- editor view unmount 时清理旧 session 的冲突面板,避免手动插入的 DOM 跨页面残留。
- 后端补充 document event filter 测试,确保资源文件路径不会匹配 Markdown 正文路径。
## 后续系统性收口
-`DocumentBufferStore` 作为正文冲突唯一状态底座,前端 session 只展示 buffer state,不自行拼第二套冲突事实。
- 为资源 tab 增加独立 resource watch 合同:按 `resourcePath` 监听资源文件,而不是复用 Markdown document event。
- 为新建页面增加创建者写入抑制或 bootstrap generation,避免未来新建空文件 watcher 与首次打开时序竞争。
-`task451` 的真实冲突 smoke 与上传/新建页面无冲突 smoke 合并成一组 local Markdown conflict regression。
## 验收
- 上传第一个/第二个附件后,主编辑区不显示 `mnote-editor-conflict-panel`
- 新建页面后,旧页面冲突 UI 不带入新页面。
- 新建页面后直接向文件树拖入文件,不显示文件冲突。
- dirty 当前 Markdown 后由外部修改同一个 `.md` 文件,仍显示冲突并保留 accept disk / keep current / diff 流程。