Skip to content

fix(editor): open a directory target as the folder window - #1030

Open
Arfrsa wants to merge 1 commit into
omdsh-dev:devfrom
Arfrsa:fix/editor-directory-fallback
Open

Arfrsa wants to merge 1 commit into
omdsh-dev:devfrom
Arfrsa:fix/editor-directory-fallback

Conversation

@Arfrsa

@Arfrsa Arfrsa commented Oct 22, 2026 •

Copy link
Copy Markdown

现象

从会话里打开一个目录时,右侧栏不会出现以该目录为根的 folder window,而是把它当文件读,并在页签里显示原始 host 错误:

"<abs path>" is a directory

两条可复现入口:

  1. 会话里的目录链接(本次报告):assistant 输出的本地 Markdown 目录链接,例如
    [legacy-implementation/](research/workspace-sections-campaign/legacy-implementation)。
    点击后 @deepseek-ai/dsh-client-ui-chat 的 openFile → fileAddressFor() →
    ctx.sidebarRight.openResource('dsh-resource://file/session/<sid>/<path>'),地址里没有「这是目录」的信息。
  2. 模型 sidebar_open 打开目录:src/client/sidebar/use-host-feeds.ts 给打开请求带了
    meta: { dir: true },但 service.openTab 的原生右栏 editor 分支
    (src/client/service.ts 1150–1167 行)把 path seed 转成 surface.openResource(address)
    时没有转发 seed.meta(只有非 editor 类型在 1184 行才带 meta),meta.dir 丢失后同样按文件读。

第三个既存触发路径是 #933(编辑器地址栏粘贴/切换目录),根因相同。

根因

  1. editor 类型以 patterns: ['dsh-resource://file/**']、priority: 'extension' 注册为文件资源查看器
    (src/client/native/index.ts 约 350 行),canOpen 只排除 HOST_OWNED_EXTS;目录地址没有扩展名,
    也通过 canOpen,于是被编辑器认领。
  2. EditorHost 只有 metaOf(tab).dir === true 才渲染文件夹树(src/client/EditorHost.tsx 137 行)。
    会话链接没有这个 meta,于是走文件加载,api.fsRead → host fs.read → readText() stat 到目录后
    抛 fs-error 400,EditorHost 把原始 message 渲染成错误页签(EditorHost.tsx 655 行)。
  3. folder root 的路径拼写必须与 host 返回的层级 key 一致(真实 GUI 复现出的第二、三层问题):
    chat 地址带的是会话相对路径,FileTree 又按请求的根路径缓存层级;而 host fs.trees 返回的
    每个层级 path 是 path.resolve 后的平台原生绝对路径。只补 meta.dir → 相对 root 卡「加载中」;
    只解析成绝对路径 → Windows 上 C:\…\workspace\research/workspace-sections-campaign/x 仍是混合斜杠,
    与 host 的全反斜杠 key 不一致,依旧卡「加载中」。

修复

  • host:fs.read / fs.readPage 在目标为目录时返回专用错误码 fs-directory
    (src/wire.ts、src/index.ts 的 readText / readPage),不再与普通 fs-error 混在一起。
  • client:src/client/editor-load.ts 新增 isDirectoryReadError(error)(检查 code === 'fs-directory');
    EditorHost 在 fetchFsRead 失败时,若是目录错误则把当前页签就地升级为插件已有的 folder window
    (meta.dir: true + TreePanel),不显示原始错误。
  • 升级时必须把目标解析成平台原生绝对路径:src/client/paths.ts 新增
    resolveNativeSidebarPath(cwd, path) —— 先 resolveSidebarPath,Windows 方言下再把 / 全部规整为 \
    并去掉尾部分隔符(drive 根保留;POSIX 保持原样,反斜杠是合法文件名字符)。promotion 用
    ctx.sessions 里的会话工作区根(fallback scope.cwd)调用它,再
    updateTab({ path: nativeAbsolute, title, meta: { dir: true } }) 同时写 path/meta。
  • 这样三条入口统一生效:会话目录链接、sidebar_open 目录、地址栏粘贴目录(同时覆盖 [Bug] 在已打开文件的编辑器中输入/切换文件夹路径报错:"..." is a directory #933)。
    模板/tab 记录沿用现有 folder 语义(meta.dir 与标题),未新增 UI、未新增 i18n key、未改公共 API 形状。

选择「错误码 + 自愈」而不是「在 openResource 里正向传 meta」的原因:会话链接在进入插件前就不知道目标是目录,
插件侧也无法同步 stat;而 open 一旦发生,folder window 本来就是该 tab 的正确归宿,fs.read 的 stat 是唯一
可靠判定点。sidebar_open 的正向 meta 透传可以另开 PR(会动 SidebarSurface.openResource 的公共参数面),
本 PR 修的是所有入口共同的失败面。

实测备注(本机 DSH 0.2.1-rc.2 + 0.28.0,真实 GUI):第一版只写 meta.dir → folder window 出现但树停在
「加载中…」;第二版加绝对路径 → Windows 上仍卡(混合斜杠);第三版加
resolveNativeSidebarPath 后,新打开目录立即展开出完整子项(docs / lib / locale / CHANGELOG.md …),
会话里的目录链接与 sidebar_open 目录走同一条 promotion 分支。

测试

新增/更新:

  • tests/editor-load.spec.ts:isDirectoryReadError 识别 fs-directory,其它 code / 非对象返回 false。
  • tests/editor-host.spec.tsx:
    • chat 链接形态的相对路径(src)在目录 fs.read 拒绝后,tab 的 path 被解析成绝对路径
      (/tmp/src)、meta 变成 { dir: true },重渲染后出现树面板搜索框,且不再显示 is a directory;
    • Windows 会话根(C:\work)下同一用例断言 promotion 结果是 C:\work\src(分隔符规整),
      钉住「混合斜杠 → 树永远加载中」的回归。
  • tests/paths.spec.ts:resolveNativeSidebarPath 的 win32 方言规整、尾部分隔符去除、drive 根保留、
    POSIX/undefined 保持原样。
  • tests/fs-read-page.spec.ts:目录分页读取的错误码从 fs-error 改为 fs-directory。

本地验证(Windows 11,Node 24,pnpm 11.8.0):

  • pnpm typecheck ✅
  • pnpm build ✅
  • pnpm lint ✅
  • pnpm exec vitest run tests/paths.spec.ts tests/editor-load.spec.ts tests/fs-read-page.spec.ts tests/editor-host.spec.tsx ✅ 78/78
  • 真实 GUI(Windows 11 + DSH 0.2.1-rc.2):本机 profile 打同一逻辑补丁后,新打开目录会立即展开出完整
    子项(会话目录链接与 sidebar_open 目录走同一条 promotion 分支),用户已确认可用。
  • pnpm test(全量):2677 passed / 8 failed / 19 skipped(7 files failed)。失败项全部是环境类,且与本次改动无关:
    Windows 符号链接权限(archive-tools / fs-operations / fs-mkdir / file-stream-route)、本机 shell 里 pnpm pack 失败(market-manifest / plugin-meta)、git smoke;
    上传并发用例为已知时序 flake(不同轮次会少/多 1 条)。
    我在干净的 dev 基线上复跑同类 6 个 spec,同类同样失败,因此不属于本 PR 引入的回归;与本改动直接相关的 spec 全绿。

关联

Fixes #1029
Related #933(同一根因的第三条触发路径;本 PR 一并覆盖)

说明

  • 本 PR 由 AI 辅助完成(定位、补丁、测试与验证),提交前经过本地 typecheck 与目标测试;全量门禁结果见 PR 评论。
  • 未修改 DSH 源码,未改依赖/peer,未新增 i18n key,未改 builder 配置。

…1029)

A path that resolves to a directory (a chat folder link, a sidebar_open
folder, or a directory pasted into the editor address bar) was read as a
file: the host answered 400 fs-error '"<path>" is a directory' and
EditorHost painted that raw message into the tab instead of the plugin's
folder window.

- fs.read / fs.readPage now answer a directory with the dedicated
  fs-directory wire code (src/wire.ts, readText/readPage).
- EditorHost promotes the tab to the folder window it already draws.
- The promoted root is a PLATFORM-NATIVE ABSOLUTE path, resolved against
  the session workspace root by the new resolveNativeSidebarPath helper
  (paths.ts): chat file addresses carry session-relative, forward-slash
  spellings, while the host keys every listing by its path.resolve'd
  absolute path and FileTree caches levels by the requested root. A
  relative root or a mixed-separator root lands in another cache key and
  the tree sits on its loading placeholder forever. The helper also drops
  a trailing separator (except a drive root) like path.resolve does.
- The fix covers all three entry points at once: conversation folder
  links (no meta.dir on the tab), sidebar_open folders (service.openTab
  drops the seed meta on the native resource route), and omdsh-dev#933's
  pasted/stepped directory path.

Tests: isDirectoryReadError unit cases, the EditorHost relative-path and
Windows-root fallback render cases, resolveNativeSidebarPath win32/POSIX
cases, and the fs.readPage directory code. typecheck + build + lint pass;
targeted suites 78/78; the full suite's remaining failures reproduce on a
clean dev checkout (Windows symlink / pnpm pack / git smokes) and are
unrelated to this change. Verified in the live GUI: a folder opened after
the patch expands its tree immediately.
@Arfrsa
Arfrsa force-pushed the fix/editor-directory-fallback branch from 19102dc to b4e9924 Compare October 22, 2026 09:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant