2026-04-13 19:37:59 +02:00
|
|
|
pub mod ai_agents;
|
2026-04-12 22:14:53 +02:00
|
|
|
pub mod app_updater;
|
2026-03-01 19:49:58 +01:00
|
|
|
pub mod claude_cli;
|
refactor: split Rust backend into sub-modules — git, theme, github, frontmatter, commands
- git.rs (1907 lines) → 7 files: mod, history, status, commit, remote, conflict, pulse
- theme.rs (1075 lines) → 4 files: mod, defaults, seed, create
- github.rs (886 lines) → 4 files: mod, api, auth, clone
- frontmatter.rs (827 lines) → 3 files: mod, yaml, ops
- lib.rs (772 lines) → lib.rs (160) + commands.rs (650)
484 tests pass, clippy clean, rustfmt clean, coverage 85.42%
2026-03-06 13:16:32 +01:00
|
|
|
mod commands;
|
2026-02-17 12:05:39 +01:00
|
|
|
pub mod frontmatter;
|
2026-02-15 12:54:11 +01:00
|
|
|
pub mod git;
|
2026-02-28 20:11:52 +01:00
|
|
|
pub mod mcp;
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-22 18:44:54 +01:00
|
|
|
pub mod menu;
|
feat: full-text search with qmd backend and SearchPanel UI (Cmd+Shift+F) (#64)
* feat: add search backend (qmd CLI) and design file
- Add search.rs: qmd integration for keyword (BM25), semantic (vsearch),
and hybrid search modes via CLI JSON output
- Register search_vault Tauri command in lib.rs
- Design file with 3 frames: empty, results, no-results states
- Fix pre-existing test failures: install @tauri-apps/plugin-opener,
add mock in test setup
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add SearchPanel UI with Cmd+Shift+F shortcut
- SearchPanel component: full-text search overlay with keyword/semantic
mode toggle, debounced search (200ms), arrow key navigation, result
count + elapsed time display, note type badges from vault entries
- Cmd+Shift+F shortcut registered in useAppKeyboard
- Mock search_vault handler in mock-tauri for browser dev mode
- SearchResult/SearchResponse types in types.ts
- Tests: 15 new tests for SearchPanel + 2 for keyboard shortcut
- scrollIntoView mock in test setup for jsdom compatibility
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: correct SearchPanel imports for Tauri/browser dual-mode
Use invoke from @tauri-apps/api/core and mockInvoke from mock-tauri
with a searchCall wrapper, fixing the TypeScript build error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make test_detect_collection_fallback robust when qmd not installed
* fix: reset localStorage between App tests to prevent view mode state leak
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:09:50 +01:00
|
|
|
pub mod search;
|
2026-02-22 13:33:32 +01:00
|
|
|
pub mod settings;
|
2026-03-25 16:20:09 +01:00
|
|
|
pub mod telemetry;
|
2026-02-14 18:20:07 +01:00
|
|
|
pub mod vault;
|
2026-03-03 00:41:46 +01:00
|
|
|
pub mod vault_list;
|
2026-02-14 18:20:07 +01:00
|
|
|
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-28 20:11:52 +01:00
|
|
|
use std::process::Child;
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-28 20:11:52 +01:00
|
|
|
use std::sync::Mutex;
|
refactor: split vault.rs into focused submodules
Break the 2338-line vault.rs into vault/ directory with 6 focused modules:
- mod.rs: core types (VaultEntry, Frontmatter), parse_md_file, scan_vault
- parsing.rs: text processing (snippet extraction, markdown stripping, date parsing)
- cache.rs: git-based incremental vault caching
- trash.rs: purge_trash with flat iterator chain
- rename.rs: note renaming with wikilink updates
- image.rs: attachment saving with filename sanitization
Also moves frontmatter update/delete wrappers to frontmatter.rs where they
belong, and converts public functions to accept &Path instead of &str.
CodeScene scores: mod.rs 10.0, parsing.rs 9.68, cache.rs 9.68,
trash.rs 9.38, rename.rs 9.68, image.rs 10.0 (all above 9.0 target).
All 169 Rust tests pass, 86.28% line coverage.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:16:21 +01:00
|
|
|
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-28 20:11:52 +01:00
|
|
|
struct WsBridgeChild(Mutex<Option<Child>>);
|
|
|
|
|
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-25 13:31:25 +01:00
|
|
|
fn log_startup_result(label: &str, result: Result<usize, String>) {
|
|
|
|
|
match result {
|
|
|
|
|
Ok(n) if n > 0 => log::info!("{}: {} files", label, n),
|
|
|
|
|
Err(e) => log::warn!("{}: {}", label, e),
|
|
|
|
|
_ => {}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-06 12:21:56 +02:00
|
|
|
/// Run startup housekeeping on the default vault (migrate legacy frontmatter, seed configs).
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-02-25 13:31:25 +01:00
|
|
|
fn run_startup_tasks() {
|
|
|
|
|
let vault_path = dirs::home_dir()
|
|
|
|
|
.map(|h| h.join("Laputa"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
if !vault_path.is_dir() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let vp_str = vault_path.to_str().unwrap_or_default();
|
|
|
|
|
log_startup_result(
|
|
|
|
|
"Migrated is_a to type on startup",
|
|
|
|
|
vault::migrate_is_a_to_type(vp_str),
|
|
|
|
|
);
|
2026-03-17 11:34:09 +01:00
|
|
|
// Migrate legacy config/agents.md → root AGENTS.md (one-time, idempotent)
|
2026-03-07 12:35:52 +01:00
|
|
|
vault::migrate_agents_md(vp_str);
|
2026-03-17 11:34:09 +01:00
|
|
|
// Seed AGENTS.md and config.md at vault root if missing
|
2026-03-07 12:35:52 +01:00
|
|
|
vault::seed_config_files(vp_str);
|
|
|
|
|
|
2026-04-12 01:35:34 +02:00
|
|
|
// Register Tolaria MCP server in Claude Code and Cursor configs
|
2026-02-28 20:11:52 +01:00
|
|
|
match mcp::register_mcp(vp_str) {
|
|
|
|
|
Ok(status) => log::info!("MCP registration: {status}"),
|
|
|
|
|
Err(e) => log::warn!("MCP registration failed: {e}"),
|
|
|
|
|
}
|
2026-02-25 13:31:25 +01:00
|
|
|
}
|
|
|
|
|
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-04-08 14:42:43 +02:00
|
|
|
fn spawn_ws_bridge(app: &mut tauri::App) {
|
2026-03-03 00:41:46 +01:00
|
|
|
use tauri::Manager;
|
|
|
|
|
let vault_path = dirs::home_dir()
|
|
|
|
|
.map(|h| h.join("Laputa"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let vp_str = vault_path.to_string_lossy().to_string();
|
|
|
|
|
match mcp::spawn_ws_bridge(&vp_str) {
|
|
|
|
|
Ok(child) => {
|
|
|
|
|
let state: tauri::State<'_, WsBridgeChild> = app.state();
|
|
|
|
|
*state.0.lock().unwrap() = Some(child);
|
|
|
|
|
}
|
|
|
|
|
Err(e) => log::warn!("Failed to start ws-bridge: {}", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 14:42:43 +02:00
|
|
|
fn setup_common_plugins(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
2026-04-08 14:01:19 +02:00
|
|
|
if cfg!(debug_assertions) {
|
|
|
|
|
app.handle().plugin(
|
|
|
|
|
tauri_plugin_log::Builder::default()
|
|
|
|
|
.level(log::LevelFilter::Info)
|
|
|
|
|
.build(),
|
|
|
|
|
)?;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.handle().plugin(tauri_plugin_dialog::init())?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
2026-04-08 14:42:43 +02:00
|
|
|
fn setup_desktop_plugins(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
2026-04-11 18:34:39 +02:00
|
|
|
setup_macos_webview_shortcut_prevention(app)?;
|
2026-04-08 14:01:19 +02:00
|
|
|
app.handle()
|
|
|
|
|
.plugin(tauri_plugin_updater::Builder::new().build())?;
|
|
|
|
|
app.handle().plugin(tauri_plugin_process::init())?;
|
|
|
|
|
app.handle().plugin(tauri_plugin_opener::init())?;
|
|
|
|
|
menu::setup_menu(app)?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 18:34:39 +02:00
|
|
|
const MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS: &[&str] = &["L"];
|
|
|
|
|
|
|
|
|
|
#[cfg(all(desktop, target_os = "macos"))]
|
|
|
|
|
fn setup_macos_webview_shortcut_prevention(
|
|
|
|
|
app: &mut tauri::App,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
use tauri_plugin_prevent_default::ModifierKey::{MetaKey, ShiftKey};
|
|
|
|
|
use tauri_plugin_prevent_default::{Flags, KeyboardShortcut};
|
|
|
|
|
|
|
|
|
|
let mut builder = tauri_plugin_prevent_default::Builder::new().with_flags(Flags::empty());
|
|
|
|
|
|
|
|
|
|
// WKWebView can swallow some browser-reserved chords before our shared
|
|
|
|
|
// renderer shortcut handler sees them. Keep this list narrow and verify
|
|
|
|
|
// every addition with native QA.
|
|
|
|
|
for key in MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS {
|
|
|
|
|
builder = builder.shortcut(KeyboardShortcut::with_modifiers(key, &[MetaKey, ShiftKey]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
app.handle().plugin(builder.build())?;
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(not(all(desktop, target_os = "macos")))]
|
|
|
|
|
fn setup_macos_webview_shortcut_prevention(
|
|
|
|
|
_app: &mut tauri::App,
|
|
|
|
|
) -> Result<(), Box<dyn std::error::Error>> {
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-08 14:42:43 +02:00
|
|
|
fn setup_app(app: &mut tauri::App) -> Result<(), Box<dyn std::error::Error>> {
|
2026-04-08 14:01:19 +02:00
|
|
|
setup_common_plugins(app)?;
|
|
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
setup_desktop_plugins(app)?;
|
|
|
|
|
|
|
|
|
|
if telemetry::init_sentry_from_settings() {
|
|
|
|
|
log::info!("Sentry initialized (crash reporting enabled)");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
{
|
|
|
|
|
run_startup_tasks();
|
|
|
|
|
spawn_ws_bridge(app);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-14 10:53:08 +02:00
|
|
|
macro_rules! app_invoke_handler {
|
|
|
|
|
() => {
|
|
|
|
|
tauri::generate_handler![
|
|
|
|
|
commands::list_vault,
|
|
|
|
|
commands::list_vault_folders,
|
|
|
|
|
commands::get_note_content,
|
|
|
|
|
commands::save_note_content,
|
|
|
|
|
commands::update_frontmatter,
|
|
|
|
|
commands::delete_frontmatter_property,
|
|
|
|
|
commands::rename_note,
|
|
|
|
|
commands::rename_note_filename,
|
|
|
|
|
commands::auto_rename_untitled,
|
|
|
|
|
commands::detect_renames,
|
|
|
|
|
commands::update_wikilinks_for_renames,
|
|
|
|
|
commands::get_file_history,
|
|
|
|
|
commands::get_modified_files,
|
|
|
|
|
commands::get_file_diff,
|
|
|
|
|
commands::get_file_diff_at_commit,
|
|
|
|
|
commands::get_vault_pulse,
|
|
|
|
|
commands::git_commit,
|
|
|
|
|
commands::get_build_number,
|
|
|
|
|
commands::get_last_commit_info,
|
|
|
|
|
commands::git_pull,
|
|
|
|
|
commands::git_push,
|
|
|
|
|
commands::git_remote_status,
|
|
|
|
|
commands::get_conflict_files,
|
|
|
|
|
commands::get_conflict_mode,
|
|
|
|
|
commands::git_resolve_conflict,
|
|
|
|
|
commands::git_commit_conflict_resolution,
|
|
|
|
|
commands::git_discard_file,
|
|
|
|
|
commands::is_git_repo,
|
|
|
|
|
commands::init_git_repo,
|
|
|
|
|
commands::check_claude_cli,
|
|
|
|
|
commands::get_ai_agents_status,
|
2026-04-14 14:16:55 +02:00
|
|
|
commands::get_vault_ai_guidance_status,
|
|
|
|
|
commands::restore_vault_ai_guidance,
|
2026-04-14 10:53:08 +02:00
|
|
|
commands::stream_claude_chat,
|
|
|
|
|
commands::stream_claude_agent,
|
|
|
|
|
commands::stream_ai_agent,
|
|
|
|
|
commands::reload_vault,
|
|
|
|
|
commands::reload_vault_entry,
|
|
|
|
|
commands::sync_note_title,
|
|
|
|
|
commands::save_image,
|
|
|
|
|
commands::copy_image_to_vault,
|
|
|
|
|
commands::delete_note,
|
|
|
|
|
commands::batch_delete_notes,
|
|
|
|
|
commands::batch_delete_notes_async,
|
|
|
|
|
commands::migrate_is_a_to_type,
|
|
|
|
|
commands::create_vault_folder,
|
|
|
|
|
commands::batch_archive_notes,
|
|
|
|
|
commands::get_settings,
|
|
|
|
|
commands::check_for_app_update,
|
|
|
|
|
commands::update_menu_state,
|
|
|
|
|
commands::trigger_menu_command,
|
2026-04-16 05:03:01 +02:00
|
|
|
commands::update_current_window_min_size,
|
2026-04-14 10:53:08 +02:00
|
|
|
commands::save_settings,
|
|
|
|
|
commands::download_and_install_app_update,
|
|
|
|
|
commands::load_vault_list,
|
|
|
|
|
commands::save_vault_list,
|
|
|
|
|
commands::clone_repo,
|
|
|
|
|
commands::search_vault,
|
|
|
|
|
commands::create_empty_vault,
|
|
|
|
|
commands::create_getting_started_vault,
|
|
|
|
|
commands::check_vault_exists,
|
|
|
|
|
commands::get_default_vault_path,
|
|
|
|
|
commands::register_mcp_tools,
|
|
|
|
|
commands::check_mcp_status,
|
|
|
|
|
commands::repair_vault,
|
|
|
|
|
commands::reinit_telemetry,
|
|
|
|
|
commands::list_views,
|
|
|
|
|
commands::save_view_cmd,
|
|
|
|
|
commands::delete_view_cmd
|
|
|
|
|
]
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
fn with_invoke_handler(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
2026-04-14 10:53:08 +02:00
|
|
|
builder.invoke_handler(app_invoke_handler!())
|
2026-04-11 10:39:08 +02:00
|
|
|
}
|
|
|
|
|
|
2026-04-08 14:01:19 +02:00
|
|
|
#[cfg(desktop)]
|
2026-04-08 14:42:43 +02:00
|
|
|
fn handle_run_event(app_handle: &tauri::AppHandle, event: &tauri::RunEvent) {
|
2026-04-08 14:01:19 +02:00
|
|
|
use tauri::Manager;
|
|
|
|
|
|
|
|
|
|
if let tauri::RunEvent::Exit = event {
|
|
|
|
|
let state: tauri::State<'_, WsBridgeChild> = app_handle.state();
|
|
|
|
|
let mut guard = state.0.lock().unwrap();
|
|
|
|
|
if let Some(ref mut child) = *guard {
|
|
|
|
|
let _ = child.kill();
|
|
|
|
|
log::info!("ws-bridge child process killed on exit");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-14 18:20:07 +01:00
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
|
|
|
pub fn run() {
|
2026-03-27 17:18:34 +01:00
|
|
|
let builder = tauri::Builder::default();
|
|
|
|
|
|
|
|
|
|
#[cfg(desktop)]
|
|
|
|
|
let builder = builder.manage(WsBridgeChild(Mutex::new(None)));
|
|
|
|
|
|
2026-04-11 10:39:08 +02:00
|
|
|
with_invoke_handler(builder)
|
2026-04-08 14:42:43 +02:00
|
|
|
.setup(setup_app)
|
2026-02-28 20:11:52 +01:00
|
|
|
.build(tauri::generate_context!())
|
|
|
|
|
.expect("error while building tauri application")
|
2026-04-08 14:01:19 +02:00
|
|
|
.run(|app_handle, event| {
|
2026-03-27 17:18:34 +01:00
|
|
|
#[cfg(desktop)]
|
2026-04-08 14:01:19 +02:00
|
|
|
handle_run_event(app_handle, &event);
|
2026-02-28 20:11:52 +01:00
|
|
|
});
|
2026-02-14 18:20:07 +01:00
|
|
|
}
|
2026-04-11 18:34:39 +02:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn macos_webview_shortcut_prevention_includes_ai_panel_shortcut() {
|
|
|
|
|
assert_eq!(MACOS_WEBVIEW_RESERVED_COMMAND_SHIFT_KEYS, ["L"]);
|
|
|
|
|
}
|
|
|
|
|
}
|