Improve local filetree view state and sidebar performance
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# local search query 每次重建索引导致大工作区搜索卡顿
|
||||
|
||||
> 状态:done
|
||||
> Owner:04-tree-domain / 03-rust-web
|
||||
> 发现日期:2026-05-27
|
||||
> 来源:Sidex 全项目 review / 本地搜索性能审查
|
||||
|
||||
## 现象
|
||||
|
||||
在 local-first 工作区中,侧边栏搜索输入虽然有前端 debounce,但后端每次 query 都会重建并写入整个 local search index。大工作区下,连续输入会触发多次递归扫描、读取 Markdown、写 `.mnote/index/search-index.json`,导致搜索和主界面卡顿。
|
||||
|
||||
## 证据
|
||||
|
||||
- `rust/crates/mnote-web/src/routes/local_search_index.rs`
|
||||
- `query_local_search_index()` 第一步调用 `rebuild_local_search_index(...)`。
|
||||
- `rebuild_local_search_index()` 递归 `collect_markdown_documents(...)` 后写入 index。
|
||||
- 前端 `sidebar-tree-runtime.js` 的搜索渲染 debounce 不能避免后端每次 query 全盘 rebuild。
|
||||
- 同文件已经存在 `refresh_local_search_index_for_path(...)` 增量更新入口,说明 watcher/单文件刷新方向已有基础,但 query path 没有复用它。
|
||||
|
||||
## Sidex / VSCode 对照
|
||||
|
||||
Sidex / VSCode 搜索路径不会把每次 query 等价为全盘同步 rebuild:
|
||||
|
||||
- quick access/search 使用 throttle、cache state 和 cancellation token。
|
||||
- Rust search crate 有 max_results、ignore-aware、binary skip 和并行扫描。
|
||||
- watcher 变化应驱动增量缓存更新,query 只消费当前 cache 或启动后台 refresh。
|
||||
|
||||
MNote 不需要照搬 VSCode 搜索 UI,但应采用同一原则:query path 不做强制全量重建。
|
||||
|
||||
## 期望行为
|
||||
|
||||
- query 优先读取已有 index。
|
||||
- index 缺失、版本不匹配、rootUri/workspaceId 不匹配时,才同步 rebuild。
|
||||
- index 过期但仍可读时,query 返回旧 index 结果,并后台触发 refresh。
|
||||
- watcher/path event 继续调用 `refresh_local_search_index_for_path(...)` 做增量更新。
|
||||
- 连续输入时,旧 query 可以被取消或自然过期,不能排队多次全盘扫描。
|
||||
|
||||
## 建议修复切片
|
||||
|
||||
1. 新增 `load_or_rebuild_local_search_index(root_path, root_uri, workspace_id)`:
|
||||
- 先 `read_local_search_index(root_path)`。
|
||||
- 若 version/rootUri/workspaceId 匹配,直接返回 index。
|
||||
- 不匹配或缺失时才调用 `rebuild_local_search_index(...)`。
|
||||
2. 将 `query_local_search_index()` 改为调用 `load_or_rebuild_local_search_index(...)`。
|
||||
3. 保留显式 refresh API 调用 `refresh_local_search_index(...)`。
|
||||
4. 加单测:
|
||||
- 首次 query 会创建 index。
|
||||
- 第二次 query 不改写 index `built_at`。
|
||||
- refresh 后 query 能读到新内容。
|
||||
- 单文件 `refresh_local_search_index_for_path(...)` 后 query 命中更新内容。
|
||||
5. 后续中期再做后台 refresh、取消令牌和搜索 worker。
|
||||
|
||||
## 验收
|
||||
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture` 通过。
|
||||
- 连续两次相同 query 时,第二次不会调用 full rebuild,不更新 `built_at`。
|
||||
- 侧边栏连续输入 5 个字符时,不出现 5 次全盘 rebuild。
|
||||
- 大目录 smoke 中搜索响应不阻塞 FileTree 展开。
|
||||
|
||||
## 修复记录
|
||||
|
||||
- 2026-05-27:新增 `load_or_rebuild_local_search_index(...)`,`query_local_search_index(...)` 优先读取已有匹配 version/rootUri/workspaceId 的索引;只有索引缺失、无效或 root/workspace 不匹配时才 full rebuild。
|
||||
- 2026-05-27:新增回归测试 `local_search_query_reads_existing_index_without_rebuilding`,验证 query 不会看到未 refresh 的磁盘新内容,增量 refresh 后才命中新内容。
|
||||
- 验证:`cargo test --manifest-path rust/Cargo.toml -p mnote-web local_search_index -- --nocapture`,4 passed。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不在本缺陷单实现全文搜索排序重构。
|
||||
- 不引入新的搜索 UI。
|
||||
- 不实现全局后台 index worker;这是后续性能设计项。
|
||||
@@ -0,0 +1,29 @@
|
||||
# FileTree 连续新建页面会展开上一个页面包目录
|
||||
|
||||
> 状态:done
|
||||
> Owner:05-editor-mainline / 04-tree-domain
|
||||
> 发现日期:2026-05-27
|
||||
> 来源:用户反馈 / Sidex Explorer 行为对照
|
||||
|
||||
## 现象
|
||||
|
||||
在 local folder 的“我的空间”中连续点击新建页面时,FileTree 会先短暂显示内部 `.md` 行,然后折叠;再次新建页面后,上一个页面包目录会被自动展开,当前页面包目录保持折叠,视觉上像是展开状态串到了旧页面。
|
||||
|
||||
## 根因
|
||||
|
||||
本地页面是 `页面名/页面名.md` 的同名目录包。新建完成后,前端 `selectSidebarFileTreeDocument(...)` 找不到折叠目录内部的 `.md` 行,会调用 `revealFileTreeResource(...)` 展开目录包并持久化展开状态。后端 `load_local_folder_file_tree_snapshot_with_reveal(...)` 也会把同名页面包目录当作普通祖先展开,导致下一次刷新时旧页面包恢复展开。
|
||||
|
||||
Sidex / VSCode Explorer 的对齐原则是:刷新 collapsed 节点只标记 stale;reveal 只展开必要祖先,不应把当前资源自身的内部实现目录当作用户展开状态持久化。
|
||||
|
||||
## 修复
|
||||
|
||||
- `local-md:` 路径解码改为完整 `~XX -> %XX -> decodeURIComponent`,避免中文路径无法匹配可见目录包行。
|
||||
- 前端选择 local markdown 时,若目标是同名页面包 `目录/目录.md` 且目录包行已可见,则直接选中/激活目录包行,不展开内部 `.md`。
|
||||
- 后端 FileTree reveal 对同名页面包只展开到页面包父级,不扫描并展开包内部 `.md`。
|
||||
- `task494-filetree-lazy-loading-dedup-smoke.js` 增加连续新建页面回归:上一个页面包不自动展开,当前页面包不展开内部 md,当前页面包行保持 active。
|
||||
|
||||
## 验证
|
||||
|
||||
- `node scripts/task494-filetree-lazy-loading-dedup-smoke.js` 先失败于上一个页面包 `expanded=true`,修复后通过。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web filetree -- --nocapture --test-threads=1`,46 passed。
|
||||
- `cargo test --manifest-path rust/Cargo.toml -p mnote-web local_file_tree -- --nocapture --test-threads=1`,4 passed。
|
||||
@@ -0,0 +1,35 @@
|
||||
# 星标 scoped 文件夹打开 Markdown 会重载并重建 FileTree
|
||||
|
||||
> 状态:done
|
||||
> Owner:05-editor-mainline / 03-rust-web
|
||||
> 发现日期:2026-05-27
|
||||
> 来源:用户反馈 / Sidex Explorer 行为对照
|
||||
|
||||
## 现象
|
||||
|
||||
从“星标置顶”点击本地文件夹 `design` 后,继续在 scoped FileTree 中打开任意 `.md` 文件,页面会明显卡顿,表现为侧栏重新构建、文件树重新加载。用户在 localhost 下也能感到卡顿,远程延迟时更明显。
|
||||
|
||||
## 根因
|
||||
|
||||
这次实际有三条慢链路叠在一起:
|
||||
|
||||
1. `navigateToDocument(...)` 在 `fileTreeScope` 存在时绕过 `window.__mnoteDocumentPaneRuntime.openPrimaryDocument(...)`,直接走 `window.location.assign(...)`。因此 scoped 文件树中打开 Markdown 不是 pane 内导航,而是整页 SSR reload。
|
||||
2. scoped 模式收到 root live snapshot 时,`renderLiveSidebarSnapshot(...)` 会调用 `refreshLocalFolderSidebarSnapshot()`,兜底重拉 scope 根 projection。
|
||||
3. tree live disabled / fallback polling 模式下,coarse `/api/tree/local-folder-watch` revision 变化会整刷 scoped FileTree;普通 Markdown mount 时,旧 `healLegacyOfficeAttachmentParagraphs()` 也会为了 legacy office 附件兼容拉 workspace root projection。
|
||||
|
||||
Sidex / VSCode Explorer 的对齐原则是:打开文件只切换 editor/input 和 selection,不重建 Explorer 数据源;Explorer 的 scoped/root view state 与文件打开解耦。
|
||||
|
||||
## 修复
|
||||
|
||||
- 允许 scoped FileTree 打开 Markdown 时继续走 `openPrimaryDocument(...)` pane 内导航。
|
||||
- URL 仍保留 `fileTreeScope`,因此刷新页面时仍能恢复 scoped Explorer。
|
||||
- scoped 模式收到非当前 scope 的 root snapshot 时只标记忽略,不再兜底重拉 scope 根。
|
||||
- fallback polling 发现 coarse root revision 变化时,对 scoped FileTree 只标记 `scope_stale`,不做即时 scope refresh。
|
||||
- legacy office 附件兼容逻辑先检查 DOM 中是否存在“单段 office 文件名”候选;普通 Markdown 不再触发 workspace root projection。
|
||||
- `task494-filetree-lazy-loading-dedup-smoke.js` 增加回归:点击 scoped 中已可见 Markdown 后,页面 JS marker 必须保留,且不应再次请求 scope 根 projection;root snapshot / coarse watch revision 也不得重拉 scope 根。
|
||||
|
||||
## 验证
|
||||
|
||||
- `node scripts/task494-filetree-lazy-loading-dedup-smoke.js` 先失败于 marker 被整页 reload 清空,修复后通过。
|
||||
- `node scripts/task494-filetree-lazy-loading-dedup-smoke.js` 继续失败于 scoped root snapshot 和 coarse watch revision 重拉 scope 根,修复后通过。
|
||||
- 真实 `/mnt/Data1T/mnote/design/05-editor-mainline/process/5-32-filetree-lazy-loading-sidex-alignment-v1.md` 链路验证通过:`scopeRootRequests=0`、`workspaceRootRequests=0`、`childrenRequests=2`,请求只剩 `design/05-editor-mainline` 与 `design/05-editor-mainline/process` 两个必要 children projection。
|
||||
Reference in New Issue
Block a user