4.2 KiB
4.2 KiB
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 重建,问题消失。
合同
- Markdown 正文 session 只订阅自己的 Markdown 文件事件。
- 非 Markdown 资源事件只用于文件树/资源投影刷新,不触发正文
externalChangePending。 - 真实正文冲突只在“同一个 Markdown documentId + 当前 session dirty/saving/saveTimer/recent input”时出现。
- 同 root 下不同 Markdown 页面必须有独立 document event channel。
- 文件树 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 流程。