Files
mnote/design/04-tree-domain/process/4-48-local-folder-markdown-resource-lifecycle-contract-v1.md
T

8.9 KiB
Raw Blame History

4-48 [process] 本地文件夹 Markdown 资源生命周期合同 v1

日期:2026-05-24

OwnerTree Domain / Local Folder / Editor Mainline

关联 bugbugs/04-tree-domain/process/4-49-filetree-drag-upload-target-parent-folder-v1.md

1. 背景

本地文件夹 MVP 已经具备 Markdown 打开、附件上传、File Tree projection、资源 tab 打开和回收站能力,但当前多个缺陷说明系统还缺少一份面向本地 Markdown 的资源生命周期合同。

核心问题不是“附件点击坏了”这类单点 UI,而是四类对象的边界没有在本地文件夹场景完全收口:

  • Markdown 正文里的链接文本。
  • 磁盘上的真实附件文件。
  • File Tree 中的 resource row。
  • 主编辑区中的 object tab / preview tab。

2. 目标

  • 固定本地 Markdown 上传、删除、缺失、打开、标题来源的产品和技术合同。
  • 保持 Markdown 纯文本语义:正文链接不隐式拥有真实文件。
  • 让本地资源默认进入主编辑区 tab,而不是依赖新窗口兜底。
  • 4-49 的多症状 bug 提供实施顺序和验收边界。

3. 非目标

  • 不实现完整跨文档反向引用索引。
  • 不自动删除所有 broken link。
  • 不把 File Tree UI 提升为资源真源。
  • 不重写完整 workbench tab 系统。
  • 不改变 cloud / Convex 资源合同,除非为了保持 open target 行为一致。

4. 合同定义

4.1 上传 intent

上传入口必须先归类为明确 intent:

  • editor.markdown.attach:用户在主编辑区上传或拖入文件。目标是当前 Markdown 页面的 resource directory。
  • filetree.folder.drop:用户把外部文件拖到 File Tree 的 folder row。目标是该 folder 的真实目录。
  • filetree.resource.import:用户未来通过菜单导入资源。目标由菜单上下文决定。

规则:

  • editor.markdown.attach 不应携带 targetRelativePath,除非产品明确支持“把正文引用指向任意文件树目录”。
  • filetree.folder.drop 必须携带 targetRelativePath
  • 后端根据 intent 选择 write_local_markdown_asset()write_local_folder_file_upload(),不能只靠 documentId 是否为空隐式决定。

4.2 Markdown 引用与真实文件

Markdown 正文链接只是引用,不是所有权声明。

  • 删除正文附件块:只删除链接文本。
  • 删除文件树附件:只删除真实文件。
  • 真实文件缺失:正文链接保留为 broken reference。
  • 自动级联删除真实文件:禁止作为默认行为。

后续可选动作:

  • “删除引用并移到回收站”:显式危险动作,需要确认。
  • “清理所有失效引用”:批量工具,需要预览 diff。
  • “重新定位文件”:把 broken link 指向新路径。

4.3 缺失资源展示

缺失资源不应静默消失。

  • Parser 必须保留原始 Markdown 链接。
  • Editor enhancer 标记缺失状态。
  • 点击缺失资源打开 missing tab 或 inline missing panel。
  • missing state 显示 root、原相对路径、最后一次检测状态。

4.4 Object tab 打开目标

本地资源默认打开到主编辑区 active tab。

  • resource:file:{rootUri}:{rootRelativePath} 是本地普通文件的最小 object identity。
  • 即使没有当前 Markdown documentlocal folder workspace 也必须有 editor host landing state。
  • PDF、Markdown 附件、图片、普通文件都走同一套 resource tab host。
  • Shift / Ctrl / Meta 或显式菜单才使用 new window。

4.5 本地 Markdown 标题来源

已确认 MVP 口径:

  • File Tree / Page Tree 标题默认来自文件名 stem。
  • 页头标题默认来自文件名 stem,用来统一“我的空间”和“本地文件夹”的行为。
  • 正文 H1 只作为正文内容,不推导 tree row title。
  • 用户可在页面设置中隐藏本地 Markdown 页头标题,默认开启,避免与正文 H1 重复。

已确认:

  • Frontmatter title 暂不覆盖 File Tree / Page Tree / 页头标题。Frontmatter title 指 Markdown 文件开头形如 --- title: Some Title --- 的元数据标题;MVP 阶段它只作为文件元数据保留,不参与本地 Markdown 标题真源。

5. 执行清单

5.1 Smoke 红灯

  • 新增 task479-local-folder-markdown-resource-lifecycle-smoke.js
  • 覆盖主编辑区上传后落盘到 page resource directory。
  • 覆盖 filetree folder drop 后落盘到目标 folder。
  • 覆盖删除真实文件后 broken link 刷新仍保留。
  • 覆盖 Backspace / Delete 删除一个附件不破坏相邻附件。
  • 覆盖刚进入 local folder 直接打开 PDF / Markdown 附件进入 active tab。
  • 覆盖 body 不因文件树滚动到底产生额外空白。
  • 覆盖本地 Markdown 文件名标题来源。

5.2 上传链路

  • 前端上传 detail 增加明确 uploadIntent
  • 后端 /api/local-folder/assets/upload 解析并校验 intent。
  • editor.markdown.attach 写入 markdown_page_resource_directory()
  • filetree.folder.drop 写入 targetRelativePath 指向的目录。
  • 上传响应返回 intent、rootRelativePath、markdownRelativePath 和 ownerDocumentId,供 smoke 断言。

5.3 删除和缺失资源

  • 主编辑区附件删除 command 只删除 Markdown 链接并保存。
  • 手柄删除复用同一 command,不做纯 DOM 隐藏。
  • Parser / serializer 保留 broken link。
  • watcher 收到真实文件删除后刷新存在性状态,不改正文。
  • 缺失资源点击进入 missing tab。

5.4 Resource tab host

  • local folder 首屏无 active document 时也渲染 editor host。
  • filetree resource open 默认 active tab。
  • PDF 使用与 cloud asset 一致的 preview chrome。
  • 本地 Markdown 附件作为 resource tab 打开,不抢占当前页面正文。
  • active row / selected row 保持在被打开资源。

5.5 标题和页面设置

  • 修正本地 Markdown 标题解析测试命名和断言。
  • File Tree / Page Tree 标题默认来自文件名。
  • 正文 H1 保留在正文,不参与标题推导。
  • 本地 Markdown 页头标题默认隐藏。
  • 页面设置新增“隐藏本地 Markdown 文件标题”的可切换 UI。
  • 设置刷新后持久生效。
  • 重命名标题时走文件重命名和 bundle 目录同步。

5.6 滚动边界

  • Workbench 根容器固定视口高度。
  • Sidebar / File Tree 是唯一纵向滚动容器。
  • resize handle wheel 不穿透到 body。
  • smoke 断言 document.scrollingElement.scrollTop 不变化。

6. 建议切片

  1. Smoke + 合同观测字段。
  2. 上传 intent 分流。
  3. 删除引用和 broken link 保留。
  4. 本地 resource tab host。
  5. PDF / Markdown 附件 tab 一致性。
  6. 标题来源和隐藏标题设置。
  7. 滚动边界。

7. 验收命令

cargo check --manifest-path rust/Cargo.toml -p mnote-web
cargo test --manifest-path rust/Cargo.toml -p mnote-web local_markdown -- --test-threads=1
PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task459-local-markdown-attachment-tab-smoke.js
PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
codegraph sync .

8. Done Gate

  • 4-49 中的 6 组用户可见症状均有对应 smoke。
  • 默认删除行为不级联删除真实附件。
  • 文件树真实删除不再导致正文链接刷新后消失。
  • 本地资源默认在主编辑区 tab 打开。
  • 本地 Markdown 标题来源和隐藏默认值经过用户确认并落地。
  • 相关 bug 记录移动到 done/ 并补验证证据。

9. 2026-05-24 执行记录

  • task479-local-folder-markdown-resource-lifecycle-smoke.js 已覆盖上传目录、filetree folder drop、broken link 保留、无 active document 附件 active tab、文件树滚动边界、文件名标题来源。
  • 修复 local folder root entry 不再复用跨 root 的 mnote_recent_page_id,避免 asset-only 工作区误进入无效 active page fallback。
  • 修复无 active document 的 local folder 首屏,补齐 workspace-level 主编辑区 resource tab host 和空 panes runtime。
  • 本地 Markdown 标题已收口为文件名来源;Frontmatter title 暂不覆盖标题真源;本地 Markdown 页头默认隐藏。
  • 验证:task479 全部通过,task459-local-markdown-attachment-tab-smoke.js 通过;Rust root_entry_local_markdowndocument_shell_renders 相关单测通过。
  • 2026-05-24 追加:task479 新增 Check 7,覆盖 Backspace 与手柄删除单个附件引用;删除后只改 Markdown 链接,不删除真实附件文件,相邻附件刷新后仍保持可点击附件块。
  • 2026-05-24 追加:来源菜单的旧“云空间”入口按当前 local-first MVP 语义改为“我的空间”,默认回到受管 my-space 本地根;新增 mnoteHome=1 防止被最近普通本地目录自动重定向接管。task441-local-folder-cloud-switch-smoke.js 已按当前语义通过。