fix local folder resource lifecycle
This commit is contained in:
@@ -1,38 +1,320 @@
|
||||
# 4-49 [process][bug] 文件树拖拽上传文件落到同级而非目标文件夹下 v1
|
||||
# 4-49 [process][bug] 本地文件夹 Markdown 附件、打开目标与标题合同缺口 v1
|
||||
|
||||
> 发现时间:2026-05-23
|
||||
>
|
||||
> 状态:`[process]`
|
||||
>
|
||||
> 关联主线:`04-tree-domain`
|
||||
> 关联主线:`04-tree-domain`、`05-editor-mainline`
|
||||
>
|
||||
> 关联设计:`design/04-tree-domain/process/4-48-local-folder-markdown-resource-lifecycle-contract-v1.md`
|
||||
|
||||
## 1. 用户可见症状
|
||||
## 1. 范围判断
|
||||
|
||||
原始记录:
|
||||
这条记录不是单个拖拽 bug,而是本地文件夹 MVP 在 `Markdown 正文引用`、`真实文件资源`、`File Tree projection`、`主编辑区 object tab` 之间的合同缺口。
|
||||
|
||||
```text
|
||||
我的空间:向文件树中拖动文件上传文件,能正常在主编辑 md 中显示,点击也能正常打开,但是见 /mnt/Data1T/mnote/tmp/image copy 65.png,文件是和主文件夹同级(应该在我的文件夹下级)。
|
||||
已按 Sidex / VSCode 行为确认的边界:
|
||||
|
||||
- 主编辑区删除 Markdown 附件块 / 链接,默认只删除正文里的引用,不删除真实附件文件。
|
||||
- 文件树右键删除真实附件,默认只删除真实文件,不自动扫描并改写所有 Markdown 正文。
|
||||
- 删除真实文件后,正文中的链接应保留为 broken reference;编辑区需要增强展示和点击失败处理,而不是静默清除链接。
|
||||
- 只有显式危险动作才可以同时删除引用和真实文件,例如后续可设计“删除引用并移到回收站”,且必须做确认和反向引用检查。
|
||||
|
||||
## 2. 用户可见症状
|
||||
|
||||
### 2.1 主编辑区拖入 / 上传附件后落盘目录不符合预期
|
||||
|
||||
复现描述:
|
||||
|
||||
1. 在“我的空间”或本地文件夹中打开一个 Markdown 页面。
|
||||
2. 向主编辑区拖入文件或上传文件。
|
||||
3. 主编辑区能显示附件,点击也能打开。
|
||||
4. 但文件落在主文件夹同级位置,预期应落在当前 Markdown 页面资源目录下。
|
||||
|
||||
截图:`/mnt/Data1T/mnote/tmp/image copy 65.png`
|
||||
|
||||
当前判断:
|
||||
|
||||
- 代码中已经有两条上传链路:
|
||||
- 文件树外部 drop 到 folder row:通过 `targetRelativePath` 走 `write_local_folder_file_upload()`。
|
||||
- 主编辑区上传 / drop:通过 `documentId` 走 `write_local_markdown_asset()`,目标应为 `markdown_page_resource_directory()`。
|
||||
- 既有 `4-46` 已把“文件树拖到文件夹 row 上传到该文件夹”标记为完成,因此当前缺口更可能在“主编辑区 Markdown 上传目标目录”和“本地 page bundle 目录推导”上,而不是单纯 filetree folder drop。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 明确区分两类 intent:
|
||||
- `filetree.folder.drop`:文件进入用户指定的文件树目录。
|
||||
- `editor.markdown.attach`:文件进入当前 Markdown 页面的资源目录。
|
||||
- 对 `editor.markdown.attach`,后端只允许写入当前 Markdown 的 page resource directory:
|
||||
- bundle 形态:`Page/Page.md` 的附件写入 `Page/`。
|
||||
- flat 形态:`Page.md` 的附件写入 `Page/`,目录不存在时创建。
|
||||
- 前端上传时不要把主编辑区 drop 误标成 folder target;有 `targetRelativePath` 时才走 folder upload。
|
||||
- smoke 需要同时覆盖“拖到文件树文件夹”和“拖到主编辑区”两个入口,避免互相掩盖。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 主编辑区上传附件后,文件树中该附件位于当前 Markdown 页面资源目录下。
|
||||
- 文件树 folder row drop 仍写入目标 folder 下,不回退到当前页面资源目录。
|
||||
- 新增或扩展 smoke 断言上传响应中的 `rootRelativePath`、正文中的相对链接、File Tree row 父子位置一致。
|
||||
|
||||
### 2.2 删除真实附件后,正文链接生命周期不稳定
|
||||
|
||||
复现描述:
|
||||
|
||||
1. 主编辑区中已有附件链接。
|
||||
2. 在文件树附件 row 上右键真实删除该附件。
|
||||
3. 主编辑区中的链接仍在,点击会显示附件不存在。
|
||||
4. 刷新后,正文里的链接消失。
|
||||
|
||||
当前判断:
|
||||
|
||||
- 链接仍在是合理行为;真实文件删除不应自动改写 Markdown 正文。
|
||||
- 刷新后链接消失是不合理行为,说明 Markdown 解析 / 回写 / 附件索引重建时把 broken local asset link 当成无效增强块过滤掉了。
|
||||
- 点击显示附件不存在也可以保留,但应在 tab 内或编辑器内给出明确缺失状态,不应走新窗口兜底。
|
||||
|
||||
建议修复:
|
||||
|
||||
- Markdown parser / serializer 必须保留 broken link 的原始文本。
|
||||
- 编辑器附件增强逻辑识别目标文件不存在时,保留链接并标记 `data-mnote-attachment-missing="true"` 或等价状态。
|
||||
- 点击 missing 附件时打开资源 tab 的 missing state,显示原路径和“文件不存在/已删除”,不删除正文。
|
||||
- 文件树真实删除后通过 watcher / event 让当前编辑器刷新附件存在性状态,但不改正文内容。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 文件树删除附件后,不刷新页面时正文链接仍可见且标记为缺失。
|
||||
- 刷新后正文链接仍存在,样式仍是 broken reference。
|
||||
- 点击缺失附件不会在新窗口打开,也不会静默失败。
|
||||
|
||||
### 2.3 主编辑区删除附件引用的序列化错误
|
||||
|
||||
复现描述:
|
||||
|
||||
1. 主编辑区有两个附件。
|
||||
2. 用 Backspace / Delete 删除后面的附件,前面的附件会变成普通文字。
|
||||
3. 删除前面的附件,后面的附件不受影响。
|
||||
4. 手柄中的删除看起来只是在 UI 上消失,刷新后又回来。
|
||||
|
||||
截图:`/mnt/Data1T/mnote/tmp/image copy 75.png`
|
||||
|
||||
当前判断:
|
||||
|
||||
- Backspace / Delete 默认只删除正文引用,这个产品语义正确。
|
||||
- “前一个附件变成文字”是编辑器 transaction 或 Markdown serializer 范围错误。
|
||||
- “手柄删除刷新后回来”是 UI 删除没有写回真实 `.md`,或写回后 parser 又按旧结构恢复。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 把附件块视为一个可删除的 editor node / mark atom;删除一个附件时只移除对应链接范围,不影响相邻附件。
|
||||
- 手柄删除复用同一条删除引用 command,不走纯 DOM 隐藏。
|
||||
- 保存前后的 Markdown 文本做最小断言:删除第二个附件只删除第二条链接,第一条仍保持链接语法和增强属性来源。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 删除前一个、后一个、中间一个附件,剩余附件都仍是可点击附件块。
|
||||
- 手柄删除后刷新页面,被删引用不再回来。
|
||||
- 真实附件文件仍保留在磁盘,除非用户选择未来的显式危险动作。
|
||||
|
||||
### 2.4 切回“云空间”进入空白 workspace
|
||||
|
||||
复现描述:
|
||||
|
||||
1. 用户打开本地文件夹。
|
||||
2. 想回到“我的空间”,点击左上角下拉中的“云空间”。
|
||||
3. 页面变成空白,整体效果是回不到我的空间。
|
||||
|
||||
当前判断:
|
||||
|
||||
- 该问题曾由 `bugs/04-tree-domain/done/4-36-local-folder-cloud-switch-empty-workspace-v1.md` 修复。
|
||||
- 如果现在仍能复现,应按回归处理:重点检查 `lastCloudWorkspaceId` 是否又被写成 synthetic `default`,以及切回 cloud 时 URL 是否残留 `rootUri`。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 先补回归 smoke 或复用 `scripts/task441-local-folder-cloud-switch-smoke.js`。
|
||||
- `openLocalFolderRoot()` 进入本地前必须记住真实 cloud workspace id,不能从 `document.body` 粗粒度推导。
|
||||
- `switchToCloudWorkspace()` 必须清理 `rootUri` / secondary local source 参数,只携带真实 cloud workspace id。
|
||||
|
||||
验收标准:
|
||||
|
||||
- cloud 有页面 -> 打开 local folder -> 点击云空间 -> 原 cloud 页面树和文件树恢复。
|
||||
- URL 不含 `rootUri`,`mnote.workspace.lastCloudWorkspaceId` 不得为 `default`。
|
||||
|
||||
### 2.5 刚打开本地文件夹时,直接打开附件只能新窗口打开
|
||||
|
||||
复现描述:
|
||||
|
||||
1. 用户刚打开本地文件夹。
|
||||
2. 未先打开任何 Markdown 文件。
|
||||
3. 直接打开任意附件,都会在新窗口打开。
|
||||
4. 只有先打开一个 `.md` 文件后,附件才能在 new tab / 主编辑区 tab 中打开。
|
||||
|
||||
当前判断:
|
||||
|
||||
- 这是主编辑区 object tab host 的 landing state 缺口。
|
||||
- 本地文件夹模式下,即使当前没有 active Markdown document,也应该有 workspace-level editor host 可承载 resource tab。
|
||||
- 附件打开不应依赖 `currentDocumentId()` 是否存在;文件树 row 已经携带 `rootUri + rootRelativePath / assetId`,足够形成 `resource:file:{rootUri}:{path}` identity。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 本地文件夹入口首屏初始化 editor host,即使没有当前文档,也显示空白 / welcome landing tab 容器。
|
||||
- 文件树附件 open intent 使用 `objectIdentity=resource:file:{rootUri}:{rootRelativePath}`,不要回退成 `window.open()`。
|
||||
- `openConvexAssetFromFileTree()` / local file open 分支按 `sourceKind=local_folder` 分流到 active tab;只有 Shift/Ctrl/Meta 或显式“新窗口打开”才新窗口。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 刚进入 local folder,直接点击 pdf / md / 图片 / 普通附件,均在主编辑区 tab 打开。
|
||||
- 未打开任何 `.md` 时也不需要新窗口兜底。
|
||||
- 文件树 row active / selected 保持在被打开附件,不跳到第一个文件。
|
||||
|
||||
### 2.6 文件树分隔线或滚动到底后出现底部空白
|
||||
|
||||
复现描述:
|
||||
|
||||
在文件树的左右拖动调整线处直接滚动页面,或文件树滚动到最下方后继续滚动,会导致下方出现空白。
|
||||
|
||||
截图:`/mnt/Data1T/mnote/tmp/image copy 66.png`
|
||||
|
||||
当前判断:
|
||||
|
||||
- 这更像 workbench layout / scroll container 边界问题,不是资源语义问题。
|
||||
- 可能是 body / main workbench 和 sidebar tree 同时可滚动,滚轮事件穿透到页面根容器后把整个页面滚到工作台高度之外。
|
||||
|
||||
建议修复:
|
||||
|
||||
- Workbench 根容器固定 `height: 100vh`,主页面禁止 body 纵向滚动。
|
||||
- Sidebar / File Tree 自己作为唯一滚动容器,`overscroll-behavior: contain`。
|
||||
- 分隔线 hover / drag 区域拦截 wheel,避免滚动事件穿透到 body。
|
||||
|
||||
验收标准:
|
||||
|
||||
- 文件树滚到最底部后继续滚动,工作台底部不出现额外空白。
|
||||
- 在左右 resize handle 上滚轮,不移动页面根滚动位置。
|
||||
- smoke 记录 `document.scrollingElement.scrollTop === 0` 或等价断言。
|
||||
|
||||
### 2.7 本地文件夹 PDF 只能新窗口打开
|
||||
|
||||
复现描述:
|
||||
|
||||
本地文件夹中的 PDF 文件只能在新窗口打开;我的空间中上传的 PDF 可以在 new tab 中打开。
|
||||
|
||||
当前判断:
|
||||
|
||||
- 与 2.5 同属本地资源 tab host 缺口。
|
||||
- PDF 是普通 resource preview tab,不应因为来源是 local folder 就退化到浏览器新窗口。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 统一本地文件打开目标:默认 active tab,显式 modifier / 菜单动作才 new window。
|
||||
- PDF tab 使用与 cloud asset 相同的 preview host,只是数据源为 `/api/local-folder/files/open?rootUri=...&path=...`。
|
||||
- 缺失文件显示 missing state,不回退到新窗口。
|
||||
|
||||
验收标准:
|
||||
|
||||
- local folder PDF 默认在主编辑区 tab 预览。
|
||||
- cloud PDF 与 local PDF 的 tab chrome、关闭、active row 行为一致。
|
||||
|
||||
### 2.8 本地 Markdown 标题来源与标题隐藏设置不清晰
|
||||
|
||||
复现描述:
|
||||
|
||||
本地文件夹 `.md` 标题解析有问题:第一行带 `#` 时,文件标题重复显示,截图见:
|
||||
|
||||
- `/mnt/Data1T/mnote/tmp/image copy 67.png`
|
||||
- `/mnt/Data1T/mnote/tmp/image copy 69.png`
|
||||
- `/mnt/Data1T/mnote/tmp/image copy 72.png`
|
||||
- `/mnt/Data1T/mnote/tmp/image copy 73.png`
|
||||
|
||||
用户期望:
|
||||
|
||||
- 文件树和页面标题更接近文件名,不要把正文第一行 H1 当成文件标题造成重复。
|
||||
- 页面设置新增全局设置:隐藏本地文件夹 Markdown 文件的文件标题。
|
||||
|
||||
当前判断:
|
||||
|
||||
- 当前代码中已有测试 `local_markdown_page_title_comes_from_file_name_not_body_heading`,但断言和注释实际倾向“有 H1 时标题来自 H1”。这与当前用户期望冲突,需要作为产品口径重新确认并修正测试命名 / 行为。
|
||||
- 对普通本地 Markdown 来说,文件名是更稳定的资源 identity;正文 H1 应保留为正文内容,不建议继续作为文件树标题真源。
|
||||
|
||||
建议修复:
|
||||
|
||||
- 本地文件夹模式下,File Tree / Page Tree 默认标题来自文件名 stem。
|
||||
- 本地 Markdown 页头标题也默认来自文件名,以统一“我的空间”和“本地文件夹”的行为。
|
||||
- Frontmatter `title` 是 Markdown 文件开头 `--- title: ... ---` 这类元数据标题;它是否覆盖页头标题需要单独决定。建议 MVP 先不覆盖 File Tree 文件名,只作为正文 metadata 或页面内显示候选。
|
||||
- 正文 H1 不参与文件名 / tree row title 推导。
|
||||
- 页面设置新增 workspace-level 或 user-level 偏好:`隐藏本地 Markdown 文件标题`。
|
||||
- 默认开启:编辑器不额外显示 MNote 页头标题,只显示 Markdown 正文。
|
||||
- 关闭:显示 MNote 页头标题,但该标题来自文件名,不能与正文 H1 混淆。
|
||||
- 修改标题时应执行文件重命名;校验非法字符、同级重名和 bundle 目录同步。
|
||||
|
||||
验收标准:
|
||||
|
||||
- `File Name.md` 且正文第一行 `# Body Heading` 时,文件树显示 `File Name.md`,正文仍显示 `# Body Heading`。
|
||||
- 不出现页头标题和正文 H1 的重复标题误导。
|
||||
- 页面设置切换“隐藏本地 Markdown 文件标题”后刷新仍生效。
|
||||
- 重命名合法文件时,文件名和 File Tree row 同步更新;非法字符 / 同级重名在输入框内提示。
|
||||
|
||||
## 3. 建议实施顺序
|
||||
|
||||
1. 先补 smoke 红灯:
|
||||
- 主编辑区上传落盘目录。
|
||||
- 删除真实附件后 broken link 保留。
|
||||
- 主编辑区删除附件引用不影响相邻附件。
|
||||
- 刚进入 local folder 直接打开附件 / PDF 进入 tab。
|
||||
- 文件树滚动到底无 body 空白。
|
||||
- 本地 Markdown 文件名标题来源。
|
||||
2. 修复资源生命周期合同:
|
||||
- 上传 intent 分流。
|
||||
- broken reference 保留和标记。
|
||||
- editor attachment delete command 持久化。
|
||||
3. 修复 local folder object tab host:
|
||||
- 没有 active document 时也能打开 resource tab。
|
||||
- PDF / md / 普通附件默认 active tab。
|
||||
4. 修复 workspace source switch 回归,如果 smoke 确认可复现。
|
||||
5. 修复 layout scroll 边界。
|
||||
6. 修正本地 Markdown 标题来源和隐藏标题设置。
|
||||
|
||||
## 4. 已确认与待确认
|
||||
|
||||
- 已确认:本地 Markdown 的 File Tree / 页头标题默认来自文件名,用来统一“我的空间”和“本地文件夹”的行为;正文 H1 永远只作为正文内容。
|
||||
- 已确认:`隐藏本地 Markdown 文件标题` 默认开启,避免本地 Markdown 页头标题与正文 H1 重复。
|
||||
- 已确认:Frontmatter `title` 暂不覆盖页头标题。Frontmatter `title` 指 Markdown 文件开头形如 `--- title: Some Title ---` 的元数据标题;MVP 阶段 File Tree / 页头标题继续以文件名为准。
|
||||
|
||||
## 5. 验证命令建议
|
||||
|
||||
```bash
|
||||
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/task441-local-folder-cloud-switch-smoke.js
|
||||
```
|
||||
|
||||
## 2. 初步判断
|
||||
后续应新增本 bug 专属 smoke,例如:
|
||||
|
||||
这是文件树拖拽上传目标解析问题,不是附件点击打开问题:
|
||||
```bash
|
||||
PLAYWRIGHT_CHROME_EXECUTABLE=/snap/bin/chromium node scripts/task479-local-folder-markdown-resource-lifecycle-smoke.js
|
||||
```
|
||||
|
||||
- 上传后的附件块能正常插入主编辑器。
|
||||
- 点击附件能正常打开。
|
||||
- 异常点是资源文件在文件树中的落点:实际落到主文件夹同级,而不是拖拽目标文件夹下级。
|
||||
## 6. 2026-05-24 执行记录
|
||||
|
||||
## 3. 待排查方向
|
||||
已修复 / 已验证:
|
||||
|
||||
- 文件树拖拽上传时的 target row / parent folder 是否正确写入 upload preflight。
|
||||
- `local_folder/assets/upload` 或 upload target plan 是否丢失目标目录。
|
||||
- 页面文件夹 / 当前页面资源归属和文件树手动目标目录之间是否存在优先级覆盖。
|
||||
- 2.1 主编辑区上传附件落盘到当前 Markdown 页面资源目录:`task479` 通过。
|
||||
- 2.2 删除真实附件后刷新,正文 broken link 保留:`task479` 通过。
|
||||
- 2.5 刚进入 local folder、无 active Markdown document 时,点击 PDF 附件进入主编辑区 resource tab:`task479` 通过,`popupCount=0`。
|
||||
- 2.7 本地 Markdown 附件 / PDF 默认 active tab:`task459` 与 `task479` 通过。
|
||||
- 2.8 本地 Markdown 标题来源:File Tree / 页头标题来自文件名,正文 H1 保留在正文;`local_markdown` 单测与 `task479` 通过。
|
||||
|
||||
## 4. 验收建议
|
||||
关键根因:
|
||||
|
||||
后续修复时应补 smoke:
|
||||
- local folder 根入口曾复用全局 `mnote_recent_page_id`。切换到另一个 root 时,旧 recent page 可能指向当前 root 不存在的 `.md`,导致 aggregate 构建失败后进入没有 resource tab host 的 workspace fallback。
|
||||
- 无 active document 的 local folder 首屏缺少 workspace-level 主编辑区 resource tab host,导致附件 row 只能依赖新窗口兜底。
|
||||
|
||||
- 在文件树中创建一个目标文件夹。
|
||||
- 将附件拖拽到该文件夹行。
|
||||
- 断言附件在文件树中位于目标文件夹下级。
|
||||
- 断言主编辑器附件块仍能点击在 tab 打开。
|
||||
本轮修复:
|
||||
|
||||
- 显式 local folder 根入口不再使用跨 root 的 recent page cookie;无有效 page 时保持无 active page landing state。
|
||||
- HomePage 无 active page 分支渲染主编辑区 tab strip、空 page panel 和 resource tab host。
|
||||
- editor island adapter 允许 `panes=[]` 时仍注册 `__mnoteDocumentPaneRuntime.openResourceInActiveTab`,只是不挂载正文编辑器。
|
||||
- `task479` Check 4 改为使用独立 asset-only local folder,确保真实覆盖“无 active Markdown document”。
|
||||
|
||||
仍在 process:
|
||||
|
||||
- 2.3 Backspace / Delete 删除相邻附件的浏览器级 smoke 尚未补齐;手柄删除保存链路已有代码修复,但仍需要真实浏览器回归。
|
||||
- 2.4 切回云空间回归本轮未复测,应继续用 `task441-local-folder-cloud-switch-smoke.js` 覆盖。
|
||||
- 页面设置中“隐藏本地 Markdown 文件标题”的可切换 UI / 持久化尚未实现;当前只落地默认隐藏。
|
||||
|
||||
Reference in New Issue
Block a user