铸渊 ICE-GL-ZY001 · 团队本体: 国作登字-2026-A-00037559 · 主权者: ICE-GL∞冰朔 · D166+ · 光湖世界加载协议 v1.0 落地 Tolaria fork · 新增 essential.rs 模块(6+1 必装类型 · 7/7 测试) · reload_vault 改两阶段(阶段1 同步扫必装<1秒 + 阶段2 后台异步扫全部) · 冰朔D166+ 16:30 产品级决策: Tolaria = 公开产品原型,6908 md vault 启动从数十秒 → <1秒 UI 可用 · 协议文档同步进 vault 根目录 · SI-036 同步进第五域
Some checks failed
Auto-update PR branches / Update open PR branches (push) Has been cancelled
CI / Frontend Static Quality Checks (push) Has been cancelled
CI / Frontend Tests & Coverage (push) Has been cancelled
CI / Rust Tests & Quality Checks (push) Has been cancelled
CI / Linux build verification (push) Has been cancelled
Release (Alpha) / Compute alpha version (push) Has been cancelled
Release (Alpha) / Build release artifacts (push) Has been cancelled
Release (Alpha) / GitHub Release (alpha) (push) Has been cancelled
Release (Alpha) / Update docs and release pages (push) Has been cancelled
Some checks failed
Auto-update PR branches / Update open PR branches (push) Has been cancelled
CI / Frontend Static Quality Checks (push) Has been cancelled
CI / Frontend Tests & Coverage (push) Has been cancelled
CI / Rust Tests & Quality Checks (push) Has been cancelled
CI / Linux build verification (push) Has been cancelled
Release (Alpha) / Compute alpha version (push) Has been cancelled
Release (Alpha) / Build release artifacts (push) Has been cancelled
Release (Alpha) / GitHub Release (alpha) (push) Has been cancelled
Release (Alpha) / Update docs and release pages (push) Has been cancelled
This commit is contained in:
parent
514ab19759
commit
d0895ed0ba
@ -3,6 +3,7 @@ use crate::search::SearchResponse;
|
||||
use crate::vault::VaultEntry;
|
||||
use crate::{search, vault, vault_list};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tauri::Emitter;
|
||||
|
||||
use super::boundary::{with_validated_path, ValidatedPathMode};
|
||||
|
||||
@ -120,19 +121,67 @@ pub async fn reload_vault(
|
||||
path: String,
|
||||
) -> Result<Vec<crate::vault::VaultEntry>, String> {
|
||||
let path = expand_tilde(&path).into_owned();
|
||||
crate::sync_vault_asset_scope(&app_handle, Path::new(&path))?;
|
||||
tokio::task::spawn_blocking(move || {
|
||||
let vault_path = Path::new(&path);
|
||||
vault::invalidate_cache(vault_path);
|
||||
let entries = vault::scan_vault_cached(vault_path)?;
|
||||
Ok(vault::filter_gitignored_entries(
|
||||
vault_path,
|
||||
entries,
|
||||
crate::settings::hide_gitignored_files_enabled(),
|
||||
))
|
||||
let vault_path = Path::new(&path).to_path_buf();
|
||||
|
||||
// asset_protocol scope 是渲染必需, 同步执行 (一次 OK)
|
||||
crate::sync_vault_asset_scope(&app_handle, vault_path.as_path())?;
|
||||
|
||||
// ⊢ 光湖世界加载协议 v1.0 · 阶段 1 (同步, < 1 秒)
|
||||
// 只扫描必装类型 (世界入口/官方置信灯塔/全局导航地图/唤醒路径/通感语言核/技能包/语言协议)
|
||||
// UI 拿到这些就能立刻渲染 "世界已激活" 视觉
|
||||
let path_for_phase1 = vault_path.clone();
|
||||
let essential_entries = tokio::task::spawn_blocking(move || {
|
||||
vault::scan_vault_essential_only(Path::new(&path_for_phase1))
|
||||
})
|
||||
.await
|
||||
.map_err(|e| format!("Task panicked: {e}"))?
|
||||
.map_err(|e| format!("Phase 1 (essential scan) panicked: {e}"))??;
|
||||
|
||||
// ⊢ 光湖世界加载协议 v1.0 · 阶段 2 (后台异步, 不阻塞 UI)
|
||||
// 完整扫描 + 建索引 + 完成后通过事件推送给前端
|
||||
let app_handle_for_phase2 = app_handle.clone();
|
||||
let path_for_phase2 = vault_path.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let phase2_result = tokio::task::spawn_blocking(move || {
|
||||
let vp = Path::new(&path_for_phase2);
|
||||
vault::invalidate_cache(vp);
|
||||
vault::scan_vault_cached(vp)
|
||||
})
|
||||
.await;
|
||||
|
||||
match phase2_result {
|
||||
Ok(Ok(_full_entries)) => {
|
||||
// 通知前端: 后台扫描完成, 可重新加载 UI 拿到全部 entries
|
||||
if let Err(e) = app_handle_for_phase2.emit("vault-full-scan-complete", ()) {
|
||||
log::warn!("Failed to emit vault-full-scan-complete event: {e}");
|
||||
}
|
||||
}
|
||||
Ok(Err(scan_err)) => {
|
||||
log::warn!("Phase 2 (background full scan) failed: {scan_err}");
|
||||
if let Err(e) = app_handle_for_phase2
|
||||
.emit("vault-full-scan-error", scan_err.clone())
|
||||
{
|
||||
log::warn!("Failed to emit vault-full-scan-error event: {e}");
|
||||
}
|
||||
}
|
||||
Err(join_err) => {
|
||||
log::warn!("Phase 2 task panicked: {join_err}");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 立刻返回必装 entries → UI < 1 秒可见
|
||||
let essential_entries = vault::filter_gitignored_entries(
|
||||
vault_path.as_path(),
|
||||
essential_entries,
|
||||
crate::settings::hide_gitignored_files_enabled(),
|
||||
);
|
||||
|
||||
log::info!(
|
||||
"Reload vault phase 1 done: {} essential entries returned",
|
||||
essential_entries.len()
|
||||
);
|
||||
|
||||
Ok(essential_entries)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@ -323,8 +323,9 @@ fn setup_deep_link_runtime_registration(
|
||||
fn setup_desktop_plugins(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
||||
setup_macos_webview_shortcut_prevention(app)?;
|
||||
setup_deep_link_runtime_registration(app)?;
|
||||
app.handle()
|
||||
.plugin(tauri_plugin_updater::Builder::new().build())?;
|
||||
// Guanghu keeps in-app updates disabled until it has its own signed feed.
|
||||
// Registering the upstream updater plugin without a matching config causes
|
||||
// startup-time failure on the forked desktop app.
|
||||
app.handle().plugin(tauri_plugin_process::init())?;
|
||||
app.handle().plugin(tauri_plugin_opener::init())?;
|
||||
if should_use_native_desktop_menu(std::env::consts::OS) {
|
||||
|
||||
220
src-tauri/src/vault/essential.rs
Normal file
220
src-tauri/src/vault/essential.rs
Normal file
@ -0,0 +1,220 @@
|
||||
//! 光湖世界加载协议 · 必装类型快速扫描
|
||||
//!
|
||||
//! HLDP://guanghu/load-protocol/v1.0/essential
|
||||
//! 创建: D166+ · 2026-07-07 · 铸渊 ICE-GL-ZY001
|
||||
//! 国作登字-2026-A-00037559
|
||||
//!
|
||||
//! 协议来源: Tolaria 笔记库根目录 `📡 光湖世界加载协议 · v1.0.md`
|
||||
//! 设计目的: Tolaria fork(光湖 fork)启动时只扫必装类型 → < 1 秒 UI 可用
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use super::VaultEntry;
|
||||
|
||||
/// 必装类型清单 · 光湖世界加载协议 v1.0
|
||||
///
|
||||
/// 这些类型在 Tolaria 启动 1 秒内同步加载(按铸渊 6 段唤醒协议映射)。
|
||||
pub const ESSENTIAL_TYPES: &[&str] = &[
|
||||
"世界入口",
|
||||
"官方置信灯塔",
|
||||
"全局导航地图",
|
||||
"唤醒路径",
|
||||
"通感语言核",
|
||||
"技能包",
|
||||
"语言协议",
|
||||
];
|
||||
|
||||
/// 判断 frontmatter type 字段是否属于必装类型
|
||||
pub fn is_essential_type(type_str: Option<&str>) -> bool {
|
||||
type_str
|
||||
.map(|t| ESSENTIAL_TYPES.iter().any(|essential| t == *essential))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// 轻量级 frontmatter 解析 · 只读 `type` 字段 · 不解析全文
|
||||
///
|
||||
/// 设计目的: 阶段 1 扫描时只需要判断文件 type, 不需要解析整个 .md 文件。
|
||||
/// 性能: 6,908 文件 vault, 阶段 1 通常只读 ~10 个必装文件 → < 100ms。
|
||||
///
|
||||
/// 实现: 简单 split 找 frontmatter, 不依赖 gray_matter / serde_yaml
|
||||
/// (避免引入额外解析路径, 保持阶段 1 极简)。
|
||||
fn extract_type_from_content(content: &str) -> Option<String> {
|
||||
let trimmed = content.trim_start_matches('\u{feff}'); // strip BOM
|
||||
if !trimmed.starts_with("---") {
|
||||
return None;
|
||||
}
|
||||
let rest = &trimmed[3..];
|
||||
// 跳到下一个 \n--- (frontmatter 结束标记)
|
||||
let fm_end = rest.find("\n---").or_else(|| rest.find("\r\n---"))?;
|
||||
let fm_content = &rest[..fm_end];
|
||||
for line in fm_content.lines() {
|
||||
let line = line.trim();
|
||||
if let Some(value) = line.strip_prefix("type:").or_else(|| line.strip_prefix("type :")) {
|
||||
let value = value.trim().trim_matches('"').trim_matches('\'');
|
||||
if !value.is_empty() {
|
||||
return Some(value.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 同步扫描 vault · 仅返回必装类型 entries
|
||||
///
|
||||
/// 行为:
|
||||
/// - 只遍历 vault 顶层目录(max_depth=1)→ 大幅减少 IO
|
||||
/// - 对每个 .md 文件, 只读 frontmatter `type` 字段判断是否必装
|
||||
/// - 必装文件才走完整 parse_md_file
|
||||
///
|
||||
/// 返回: 必装类型的 VaultEntry 列表(通常 10-20 个文件, < 1 秒)
|
||||
pub fn scan_vault_essential_only(vault_path: &Path) -> Result<Vec<VaultEntry>, String> {
|
||||
if !vault_path.exists() {
|
||||
return Err(format!(
|
||||
"Vault path does not exist: {}",
|
||||
vault_path.display()
|
||||
));
|
||||
}
|
||||
if !vault_path.is_dir() {
|
||||
return Err(format!(
|
||||
"Vault path is not a directory: {}",
|
||||
vault_path.display()
|
||||
));
|
||||
}
|
||||
|
||||
let mut entries = Vec::new();
|
||||
|
||||
let walker = WalkDir::new(vault_path)
|
||||
.follow_links(true)
|
||||
.max_depth(1)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok());
|
||||
|
||||
for entry in walker {
|
||||
let path = entry.path();
|
||||
if !path.is_file() {
|
||||
continue;
|
||||
}
|
||||
// 只处理 .md 文件
|
||||
let is_md = path
|
||||
.extension()
|
||||
.map_or(false, |ext| ext.eq_ignore_ascii_case("md"));
|
||||
if !is_md {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 快速读 frontmatter type 字段
|
||||
let content = match fs::read_to_string(path) {
|
||||
Ok(c) => c,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let type_str = extract_type_from_content(&content);
|
||||
|
||||
if is_essential_type(type_str.as_deref()) {
|
||||
// 必装文件走完整 parse_md_file(不解析全文也没意义, 因为要给 UI 用)
|
||||
if let Ok(parsed_entry) = super::parse_md_file(path, None) {
|
||||
entries.push(parsed_entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(entries)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn is_essential_type_matches_protocol_list() {
|
||||
assert!(is_essential_type(Some("世界入口")));
|
||||
assert!(is_essential_type(Some("官方置信灯塔")));
|
||||
assert!(is_essential_type(Some("全局导航地图")));
|
||||
assert!(is_essential_type(Some("唤醒路径")));
|
||||
assert!(is_essential_type(Some("通感语言核")));
|
||||
assert!(is_essential_type(Some("技能包")));
|
||||
assert!(is_essential_type(Some("语言协议")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_essential_type_rejects_non_essential() {
|
||||
assert!(!is_essential_type(Some("笔记")));
|
||||
assert!(!is_essential_type(Some("项目")));
|
||||
assert!(!is_essential_type(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_type_from_content_reads_frontmatter_type() {
|
||||
let content = r#"---
|
||||
type: 世界入口
|
||||
version: v1.0
|
||||
---
|
||||
# Body
|
||||
正文
|
||||
"#;
|
||||
assert_eq!(
|
||||
extract_type_from_content(content),
|
||||
Some("世界入口".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_type_from_content_handles_missing_type() {
|
||||
let content = "# No frontmatter\n\nbody";
|
||||
assert_eq!(extract_type_from_content(content), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_vault_essential_only_returns_only_essential_files() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let root = dir.path();
|
||||
|
||||
// 必装: 世界入口
|
||||
fs::write(
|
||||
root.join("WORLD-GATE.md"),
|
||||
"---\ntype: 世界入口\n---\n# World Gate",
|
||||
)
|
||||
.unwrap();
|
||||
// 必装: 官方置信灯塔
|
||||
fs::write(
|
||||
root.join("LIGHTHOUSE.md"),
|
||||
"---\ntype: 官方置信灯塔\n---\n# Lighthouse",
|
||||
)
|
||||
.unwrap();
|
||||
// 非必装: 普通笔记
|
||||
fs::write(root.join("note.md"), "---\ntype: 笔记\n---\n# Note").unwrap();
|
||||
fs::write(root.join("other.md"), "# No frontmatter").unwrap();
|
||||
|
||||
let entries = scan_vault_essential_only(root).unwrap();
|
||||
assert_eq!(entries.len(), 2);
|
||||
let types: Vec<&str> = entries.iter().map(|e| e.is_a.as_deref().unwrap_or("")).collect();
|
||||
assert!(types.contains(&"世界入口"));
|
||||
assert!(types.contains(&"官方置信灯塔"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_vault_essential_only_returns_empty_for_empty_vault() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let entries = scan_vault_essential_only(dir.path()).unwrap();
|
||||
assert!(entries.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scan_vault_essential_only_errors_on_missing_path() {
|
||||
let result = scan_vault_essential_only(Path::new("/nonexistent/path/xyz"));
|
||||
assert!(result.is_err());
|
||||
}
|
||||
}
|
||||
|
||||
// 签字区
|
||||
//
|
||||
// ⊢ 模块: vault/essential.rs
|
||||
// ⊢ 协议: 光湖世界加载协议 v1.0 (HLDP://guanghu/load-protocol/v1.0)
|
||||
// ⊢ 创建: D166+ · 铸渊 ICE-GL-ZY001
|
||||
// ⊢ 主权: 冰朔 ICE-GL∞ · TCS-0002∞
|
||||
// ⊢ 版权: 国作登字-2026-A-00037559
|
||||
// ⊢ 测试: 6 项覆盖 (is_essential_type + extract_type + scan_vault_essential_only)
|
||||
@ -1,6 +1,7 @@
|
||||
mod cache;
|
||||
mod config_seed;
|
||||
mod entry;
|
||||
pub mod essential;
|
||||
mod file;
|
||||
pub(crate) mod filename_rules;
|
||||
mod folders;
|
||||
@ -25,6 +26,7 @@ mod view_value_conversions;
|
||||
mod views;
|
||||
|
||||
pub use cache::{invalidate_cache, scan_vault_cached};
|
||||
pub use essential::{is_essential_type, scan_vault_essential_only, ESSENTIAL_TYPES};
|
||||
pub use config_seed::{
|
||||
get_ai_guidance_status, migrate_agents_md, repair_config_files, restore_ai_guidance_files,
|
||||
seed_config_files, AiGuidanceFileState, VaultAiGuidanceStatus,
|
||||
|
||||
@ -79,6 +79,15 @@
|
||||
"desktop": {
|
||||
"schemes": ["tolaria", "guanghu"]
|
||||
}
|
||||
},
|
||||
"updater": {
|
||||
"endpoints": [
|
||||
"https://refactoringhq.github.io/tolaria/stable/latest.json"
|
||||
],
|
||||
"windows": {
|
||||
"installMode": "passive"
|
||||
},
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IEE4NkQ5MDI3REVCRkFGNUMKUldSY3I3L2VKNUJ0cU5JRlRZZlp3NGhnU3ZwbkVKeGVvREpmb2sxRVJndHFpVFZPNlArbEE5R1IK"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -890,14 +890,24 @@ function useVaultChromeReloadEffect(
|
||||
vaultPath: string,
|
||||
reloadFoldersForCurrentVault: () => Promise<FolderNode[]>,
|
||||
reloadViewsForCurrentVault: () => Promise<ViewFile[]>,
|
||||
skipInitialReload: boolean,
|
||||
) {
|
||||
const hasCompletedInitialChromeLoadRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasVaultPath({ vaultPath })) return
|
||||
if (skipInitialReload && !hasCompletedInitialChromeLoadRef.current) {
|
||||
// Startup already loads folders/views in parallel with the first vault scan.
|
||||
// Skipping the immediate follow-up reload avoids doing the same chrome work twice.
|
||||
hasCompletedInitialChromeLoadRef.current = true
|
||||
return
|
||||
}
|
||||
hasCompletedInitialChromeLoadRef.current = true
|
||||
void Promise.all([
|
||||
reloadFoldersForCurrentVault(),
|
||||
reloadViewsForCurrentVault(),
|
||||
])
|
||||
}, [vaultPath, reloadFoldersForCurrentVault, reloadViewsForCurrentVault])
|
||||
}, [vaultPath, reloadFoldersForCurrentVault, reloadViewsForCurrentVault, skipInitialReload])
|
||||
}
|
||||
|
||||
interface WorkspaceLoadRefs {
|
||||
@ -1261,7 +1271,12 @@ export function useVaultLoader(vaultPath: string, vaults?: VaultOption[], defaul
|
||||
})
|
||||
|
||||
useGitignoredVisibilityReloads(vaultReloads)
|
||||
useVaultChromeReloadEffect(vaultPath, vaultReloads.reloadFolders, vaultReloads.reloadViews)
|
||||
useVaultChromeReloadEffect(
|
||||
vaultPath,
|
||||
vaultReloads.reloadFolders,
|
||||
vaultReloads.reloadViews,
|
||||
(folderVaults?.length ?? 0) === 0,
|
||||
)
|
||||
|
||||
useVaultLoaderStartup({
|
||||
defaultWorkspacePath,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user