Skip to content

Feat/restore fs events refresh - #433

Open
TheHeartFickle wants to merge 3 commits into
omdsh-dev:mainfrom
TheHeartFickle:feat/restore-fs-events-refresh
Open

Feat/restore fs events refresh#433
TheHeartFickle wants to merge 3 commits into
omdsh-dev:mainfrom
TheHeartFickle:feat/restore-fs-events-refresh

Conversation

@TheHeartFickle

Copy link
Copy Markdown
Contributor

背景

文件树只能靠手动刷新按钮。外部文件变化(git clone、编辑器增删文件、构建产物)在点击刷新前对侧边栏不可见。

方案

按仓库管理员在 #196 的建议、以 #137 的契约为主线,恢复被动事件驱动的
文件树自动刷新:

  • host 使用 node:fs 原生 fs.watch 实现 reference-counted hub,不新增依赖。
  • 客户端通过 /sidebar/ws/fs-events 发送 watch/unwatch,host 推送 {type:'change', path},客户端只 revalidate 变更目录,无轮询、无整树闪烁。
  • 断线使用指数退避重连,重建后对可见目录做一次无闪烁 revalidate。

契约增强:Windows 单根递归 watcher

Windows 上如果按每个可见目录分别打开 fs.watch 句柄,会因子目录
objbin 等)仍被 watch 而阻止父目录改名/移动。本实现改为对会话
cwd 使用单个递归 fs.watch 根句柄,再将递归事件映射回客户端实际 watch
的目录,避免 Windows 的句柄阻塞问题。

同时忽略 .gitnode_modulesdistbuild.nextoutcoverage.cache.config.vscode.idea.vs.svn.hgAppData.venvvenv__pycache__objTemptmp 等不需要随时刷新的目录;这些目录仍可手动刷新。

改动

  • src/index.tsFsWatchHub/sidebar/ws/fs-events upgrade route、 attachFsEvents、teardown。
  • src/client/FileTree.tsx:无闪烁 revalidate、可见目录跟踪、自动刷新 socket、指数退避重连;手动刷新也改为原位 revalidate,不再清空整棵树。
  • tests/file-tree-refresh.spec.tsx:折叠目录后刷新再重新展开必须拿到 新数据,不能读陈旧缓存。

测试

  • pnpm typecheck
  • pnpm build
  • pnpm vitest run tests/file-tree-refresh.spec.tsx

@Menghuan1918 Menghuan1918 added enhancement New feature or request P2 中:一般功能/优化 area/explorer File tree / explorer: navigation, context menu, search labels Aug 27, 2026
@TheHeartFickle

Copy link
Copy Markdown
Contributor Author

已修复 PR #433 的 CI 类型校验失败。

根因

upstream/main 已合入 #372 fix(cwd): resolve detached session cwd via persistence index,该提交把 sessionCwdOf 从同步函数改成了:

async function sessionCwdOf(...): Promise<string>

attachFsEvents 里仍按旧签名同步调用:

const cwd = sessionCwdOf(ctx, sessionId, ...)

所以在 GitHub 合并 upstream/main 后的 CI 中,cwd 被推断成 Promise<string>,导致后续传参类型报错:

  • src/index.ts(1479,23)
  • src/index.ts(1483,36)
  • src/index.ts(1484,75)

修复

attachFsEvents 改为 async,并在获取会话 cwd 时 await sessionCwdOf(...)

async function attachFsEvents(
  ctx: Context,
  hub: FsWatchHub,
  ws: WebSocket,
  req: SidebarHttpRequest,
): Promise<void> {
  ...
  const cwd = await sessionCwdOf(ctx, sessionId, url.searchParams.get('cwd') ?? undefined)
  ...
}

路由调用处本来就用 void attachFsEvents(...),无需改调用侧。

验证

已用临时分支把 upstream/main 合并进来模拟 PR #433 的 CI 环境:

  • pnpm typecheck
  • pnpm vitest run tests/fs-watch-hub.spec.ts tests/file-tree-refresh.spec.tsx ✅(6 tests)
  • pnpm vitest run tests/smoke.spec.ts -t "mounts the fenced routes"

@TheHeartFickle

Copy link
Copy Markdown
Contributor Author

@Menghuan1918

文件树自 v0.12.2 起不再自动刷新,只能靠手动刷新按钮。外部文件变化
(git clone、编辑器增删文件、构建产物)在点击刷新前对侧边栏不可见。

按仓库管理员在 omdsh-dev#196 的建议、以 omdsh-dev#137 的契约为主线,恢复被动事件驱动的
文件树自动刷新:

- host 使用 `node:fs` 原生 `fs.watch` 实现 reference-counted hub,不新增依赖。
- 客户端通过 `/sidebar/ws/fs-events` 发送 `watch/unwatch`,host 推送
  `{type:'change', path}`,客户端只 revalidate 变更目录,无轮询、无整树闪烁。
- 断线使用指数退避重连,重建后对可见目录做一次无闪烁 revalidate。

Windows 上如果按每个可见目录分别打开 `fs.watch` 句柄,会因子目录
(`obj`、`bin` 等)仍被 watch 而阻止父目录改名/移动。本实现改为对会话
cwd 使用单个递归 `fs.watch` 根句柄,再将递归事件映射回客户端实际 watch
的目录,避免 Windows 的句柄阻塞问题。

同时忽略 `.git`、`node_modules`、`dist`、`build`、`.next`、`out`、
`coverage`、`.cache`、`.config`、`.vscode`、`.idea`、`.vs`、`.svn`、
`.hg`、`AppData`、`.venv`、`venv`、`__pycache__`、`obj`、`Temp`、`tmp`
等不需要随时刷新的目录;这些目录仍可手动刷新。

- `src/index.ts`:`FsWatchHub`、`/sidebar/ws/fs-events` upgrade route、
  `attachFsEvents`、teardown。
- `src/client/FileTree.tsx`:无闪烁 revalidate、可见目录跟踪、自动刷新
  socket、指数退避重连;手动刷新也改为原位 revalidate,不再清空整棵树。
- `tests/file-tree-refresh.spec.tsx`:折叠目录后刷新再重新展开必须拿到
  新数据,不能读陈旧缓存。

- `pnpm typecheck`
- `pnpm build`
- `pnpm vitest run tests/file-tree-refresh.spec.tsx`
## 背景

子代理审查提交 e4a47a2 后发现的 P2 问题,影响自动刷新在某些路径/平台
下失效或出现句柄泄漏、越权监听风险。

## 修复

- 忽略目录改为按“工作区相对路径”判断:会话 cwd 自身不会被祖先路径中的
  tmp/temp/build/dist 等词误伤,避免整个工作区自动刷新被关闭。
- Windows 递归 watcher 不再吞掉 `.git`/`node_modules` 等忽略目录本身的
  创建/删除/重命名事件,父目录仍能刷新;只过滤这些目录内部的深层事件。
- watch 路径先经 `ensureWorkspacePath` realpath 校验,拒绝通过符号链接
  逃逸到会话工作区外的目录。
- 非 Windows 逐目录 watcher 出错后保留订阅状态并自动重试,目录重建后
  自动刷新可恢复;同时修正 `dispose()` 后仍可创建新 watcher 的句柄泄漏。

## 回归测试

- 新增 `tests/fs-watch-hub.spec.ts`:
  - 直接目录变更通知
  - 忽略目录自身不订阅
  - 忽略目录创建仍刷新父级
  - watch/unwatch 引用计数
  - dispose 后不再新建 watcher
- 更新 `tests/smoke.spec.ts`:注册路由断言加入 `/sidebar/ws/fs-events`。

## 验证

- `pnpm typecheck`
- `pnpm build`
- `pnpm vitest run tests/fs-watch-hub.spec.ts tests/file-tree-refresh.spec.tsx`
@TheHeartFickle
TheHeartFickle force-pushed the feat/restore-fs-events-refresh branch from 67483f1 to a4562db Compare September 3, 2026 08:50
@TheHeartFickle

Copy link
Copy Markdown
Contributor Author

@Menghuan1918 PR #433 已完成与最新 mainad392ce)的冲突处理。

冲突处理方式

采用 rebase 到最新 main(非 merge),冲突在原提交内解决,PR 历史仍为 3 个提交:

  • src/index.ts(import 区):上游新增的 resolveSessionPath(session-path,fix(cwd): resolve detached session cwd via persistence index, not host process cwd #372 相关)与本 PR 新增的 isWithin(fs-events 越界校验)落在同一 import 块,两侧都保留。
  • src/client/FileTree.tsx(refresh 语义区):上游新增的 retryDirFenceErrorNotice 的拦截重试依赖它)与本 PR 的 refreshVisible/expandedKey(原地 revalidate)落在同一块,两者都保留;注释采用本 PR 版本——tick 语义已从「清缓存重拉」改为「原地重载」,沿用上游旧注释会自相矛盾。
  • tests/file-tree-refresh.spec.tsx:上游将 FileTreestore 变为必填 prop,测试补传 store: createSidebarStore()(已 fixup 进第一个提交,不留孤立 fix commit)。

#469 的关系

#469 新增的 window focusdsh-sidebar:refresh-files 触发源完整保留:二者 bump 的 refreshTick 现在走本 PR 的原地 revalidate 路径,即这两个触发也从「清缓存重拉(整树闪烁)」升级为「无闪烁原地更新」。

关于 #344 的两个核心验收点

两条均已在代码落地:

  1. 按展开目录精确 fs.watch 监听:非 Windows 平台严格按 [Feature] 文件树基于 fs.watch 的自动刷新(含已验证的参考实现:按展开目录精确监听 + 无闪烁原地更新) #344 / feat(explorer): restore passive fs-events file tree refresh #137 的方式实现——每个可见目录一个非递归 fs.watch,跨 socket 引用计数,收起即撤销(FsWatchHub 非 Windows 分支)。Windows 例外及理由见 PR 描述「契约增强」一节:按目录开句柄会阻止父目录改名/移动,故 win32 用单个递归根句柄 + 事件映射回被 watch 目录——这是平台分流,不是全局改递归。
  2. stale-while-revalidate 无闪烁原地更新loadDir(dir, true) 保留旧列表后台重拉,requestVersion 防竞态,listing 到达后原子替换;手动刷新与 refreshTick 同样走原地 revalidate,不再清空整树。

#344 方案之上的增强:按目录 {type:'change', path} 通知(客户端只 revalidate 变更目录,而非整树);断线指数退避重连,重连后对可见目录做一次无闪烁 revalidate 弥补断线盲区;realpath + ensureWorkspacePath 防符号链接逃逸越界;.git / node_modules / dist / build 等忽略集(这些目录仍可手动刷新)。

验证

  • pnpm typecheck
  • pnpm vitest run tests/fs-watch-hub.spec.ts tests/file-tree-refresh.spec.tsx tests/smoke.spec.ts → 58 passed / 1 skipped ✅
  • pnpm build

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/explorer File tree / explorer: navigation, context menu, search enhancement New feature or request P2 中:一般功能/优化

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants