收口本地工作区清理与资源投影

清理历史 Electron、Graphify、沙箱和截图等仓库跟踪残留,补充 CodeGraph 与 Convex active deploy source 协作说明。

新增 tree-first 下一阶段设计稿和 2026-05-20 清理总结,记录本地工作区、路径身份和 Zed/Lapce/VSCode 参考收口方向。

扩展 Rust Web 本地文件夹、DocumentBuffer、mindmap 资源、tree runtime 和页面聚合链路,并补充 task455 local-folder mindmap clean smoke。

验证:git diff --check 通过;pnpm store status --store-dir .pnpm-store 通过;npm ls --depth=0 --json 通过;find -L node_modules 未发现断链。cargo test -p mnote-web 当前 418 passed / 35 failed。
This commit is contained in:
lix-2026
2026-05-20 10:43:38 +08:00
parent 90818cdf75
commit b4c8bcb647
73 changed files with 8073 additions and 8240 deletions
@@ -1,3 +1,4 @@
use crate::document_buffer_store::BufferStore;
use crate::routes::{local_workspace_id_from_root_uri, refresh_local_search_index_for_path};
use notify::event::ModifyKind;
use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
@@ -12,6 +13,7 @@ use tokio::sync::{broadcast, mpsc, oneshot};
#[derive(Clone)]
pub struct LocalFolderWatcherRegistry {
inner: Arc<LocalFolderWatcherRegistryInner>,
buffer_store: BufferStore,
}
impl std::fmt::Debug for LocalFolderWatcherRegistry {
@@ -23,11 +25,12 @@ impl std::fmt::Debug for LocalFolderWatcherRegistry {
}
impl LocalFolderWatcherRegistry {
pub fn new() -> Self {
pub fn new(buffer_store: BufferStore) -> Self {
Self {
inner: Arc::new(LocalFolderWatcherRegistryInner {
entries: Mutex::new(HashMap::new()),
}),
buffer_store,
}
}
@@ -36,7 +39,10 @@ impl LocalFolderWatcherRegistry {
canonical_root: &Path,
) -> Result<LocalFolderWatcherSubscription, String> {
let key = canonical_root_uri(canonical_root);
let channel = self.inner.get_or_create_channel(&key, canonical_root)?;
let buffer_store = self.buffer_store.clone();
let channel = self
.inner
.get_or_create_channel(&key, canonical_root, buffer_store)?;
channel.subscriber_count.fetch_add(1, Ordering::SeqCst);
Ok(LocalFolderWatcherSubscription {
receiver: channel.sender.subscribe(),
@@ -63,6 +69,7 @@ impl LocalFolderWatcherRegistryInner {
&self,
key: &str,
canonical_root: &Path,
buffer_store: BufferStore,
) -> Result<Arc<LocalFolderWatchChannel>, String> {
if let Some(existing) = self
.entries
@@ -76,7 +83,7 @@ impl LocalFolderWatcherRegistryInner {
let channel = Arc::new(LocalFolderWatchChannel::new(
key.to_string(),
spawn_local_folder_watcher(key, canonical_root.to_path_buf())?,
spawn_local_folder_watcher(key, canonical_root.to_path_buf(), buffer_store)?,
));
let mut entries = self.entries.lock().expect("registry lock");
@@ -157,6 +164,7 @@ impl Drop for LocalFolderWatcherSubscriptionGuard {
fn spawn_local_folder_watcher(
root_uri: &str,
canonical_root: PathBuf,
buffer_store: BufferStore,
) -> Result<(broadcast::Sender<Value>, oneshot::Sender<()>), String> {
let (event_sender, mut event_receiver) = mpsc::unbounded_channel::<notify::Result<Event>>();
let mut watcher = RecommendedWatcher::new(
@@ -174,6 +182,7 @@ fn spawn_local_folder_watcher(
let (shutdown_tx, mut shutdown_rx) = oneshot::channel::<()>();
let sender_for_task = sender.clone();
let root_uri_for_task = root_uri.to_string();
let buffer_store_for_task = buffer_store.clone();
tokio::spawn(async move {
let _watcher = watcher;
loop {
@@ -206,11 +215,29 @@ fn spawn_local_folder_watcher(
if !is_markdown_path(&path) {
continue;
}
// 外部文件变更→更新 BufferStore
let document_id =
format!("local-md:{}", encode_local_id_segment(&relative_path));
if let Ok(ws_id) =
local_workspace_id_from_root_uri(&root_uri_for_task)
{
let ws_path =
crate::document_buffer_store::build_local_folder_workspace_path(
&ws_id,
&root_uri_for_task,
&relative_path,
&document_id,
);
buffer_store_for_task
.mark_external_modified(&ws_path, Some("external-editor".into()));
}
let payload = json!({
"sourceKind": "local_folder",
"rootUri": root_uri_for_task,
"relativePath": relative_path,
"documentId": format!("local-md:{}", encode_local_id_segment(&relative_path)),
"documentId": document_id,
"eventKind": format!("{:?}", event.kind),
"revision": event_revision(&path),
});
@@ -319,6 +346,7 @@ mod tests {
is_local_search_index_path, refresh_local_search_index_for_event, should_emit_event_kind,
LocalFolderWatcherRegistry,
};
use crate::document_buffer_store::BufferStore;
use notify::event::{AccessKind, CreateKind, DataChange, ModifyKind};
use notify::EventKind;
@@ -337,7 +365,7 @@ mod tests {
#[tokio::test]
async fn same_root_subscribers_share_single_watcher() {
let registry = LocalFolderWatcherRegistry::new();
let registry = LocalFolderWatcherRegistry::new(BufferStore::new());
let root = test_root("shared");
let first = registry.subscribe(&root).expect("first subscription");
@@ -358,7 +386,7 @@ mod tests {
#[tokio::test]
async fn different_roots_create_independent_watchers() {
let registry = LocalFolderWatcherRegistry::new();
let registry = LocalFolderWatcherRegistry::new(BufferStore::new());
let first_root = test_root("first");
let second_root = test_root("second");