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

4.2 KiB
Raw Blame History

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 流程。