Initialize Tauri iOS support with conditional compilation: - Desktop-only features (git CLI, menu, MCP, Claude CLI) gated behind #[cfg(desktop)] - Mobile stubs return graceful errors so frontend degrades smoothly - Vault read/write, AI chat, search, settings work unchanged on both platforms - Xcode project generated, Rust cross-compiles cleanly to aarch64-apple-ios-sim - All 581 Rust tests and 2201 frontend tests still pass Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
196 lines
6.3 KiB
Rust
196 lines
6.3 KiB
Rust
pub mod ai_chat;
|
|
pub mod claude_cli;
|
|
mod commands;
|
|
pub mod frontmatter;
|
|
pub mod git;
|
|
pub mod github;
|
|
pub mod mcp;
|
|
#[cfg(desktop)]
|
|
pub mod menu;
|
|
pub mod search;
|
|
pub mod settings;
|
|
pub mod telemetry;
|
|
pub mod vault;
|
|
pub mod vault_list;
|
|
|
|
#[cfg(desktop)]
|
|
use std::process::Child;
|
|
#[cfg(desktop)]
|
|
use std::sync::Mutex;
|
|
|
|
#[cfg(desktop)]
|
|
struct WsBridgeChild(Mutex<Option<Child>>);
|
|
|
|
#[cfg(desktop)]
|
|
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),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
/// Run startup housekeeping on the default vault (purge old trash, migrate legacy frontmatter).
|
|
#[cfg(desktop)]
|
|
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(
|
|
"Purged trashed files on startup",
|
|
vault::purge_trash(vp_str).map(|d| d.len()),
|
|
);
|
|
log_startup_result(
|
|
"Migrated is_a to type on startup",
|
|
vault::migrate_is_a_to_type(vp_str),
|
|
);
|
|
// Migrate legacy config/agents.md → root AGENTS.md (one-time, idempotent)
|
|
vault::migrate_agents_md(vp_str);
|
|
// Seed AGENTS.md and config.md at vault root if missing
|
|
vault::seed_config_files(vp_str);
|
|
|
|
// Register Laputa MCP server in Claude Code and Cursor configs
|
|
match mcp::register_mcp(vp_str) {
|
|
Ok(status) => log::info!("MCP registration: {status}"),
|
|
Err(e) => log::warn!("MCP registration failed: {e}"),
|
|
}
|
|
}
|
|
|
|
#[cfg(desktop)]
|
|
fn spawn_ws_bridge(app: &mut tauri::App) {
|
|
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),
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
let builder = tauri::Builder::default();
|
|
|
|
#[cfg(desktop)]
|
|
let builder = builder.manage(WsBridgeChild(Mutex::new(None)));
|
|
|
|
builder
|
|
.setup(|app| {
|
|
if cfg!(debug_assertions) {
|
|
app.handle().plugin(
|
|
tauri_plugin_log::Builder::default()
|
|
.level(log::LevelFilter::Info)
|
|
.build(),
|
|
)?;
|
|
}
|
|
|
|
app.handle().plugin(tauri_plugin_dialog::init())?;
|
|
|
|
#[cfg(desktop)]
|
|
{
|
|
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)?;
|
|
}
|
|
|
|
if telemetry::init_sentry_from_settings() {
|
|
log::info!("Sentry initialized (crash reporting enabled)");
|
|
}
|
|
|
|
#[cfg(desktop)]
|
|
{
|
|
run_startup_tasks();
|
|
spawn_ws_bridge(app);
|
|
}
|
|
|
|
Ok(())
|
|
})
|
|
.invoke_handler(tauri::generate_handler![
|
|
commands::list_vault,
|
|
commands::get_note_content,
|
|
commands::save_note_content,
|
|
commands::update_frontmatter,
|
|
commands::delete_frontmatter_property,
|
|
commands::rename_note,
|
|
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::ai_chat,
|
|
commands::check_claude_cli,
|
|
commands::stream_claude_chat,
|
|
commands::stream_claude_agent,
|
|
commands::reload_vault,
|
|
commands::reload_vault_entry,
|
|
commands::sync_note_title,
|
|
commands::save_image,
|
|
commands::copy_image_to_vault,
|
|
commands::purge_trash,
|
|
commands::delete_note,
|
|
commands::batch_delete_notes,
|
|
commands::empty_trash,
|
|
commands::migrate_is_a_to_type,
|
|
commands::flatten_vault,
|
|
commands::vault_health_check,
|
|
commands::batch_archive_notes,
|
|
commands::batch_trash_notes,
|
|
commands::get_settings,
|
|
commands::update_menu_state,
|
|
commands::save_settings,
|
|
commands::load_vault_list,
|
|
commands::save_vault_list,
|
|
commands::github_list_repos,
|
|
commands::github_create_repo,
|
|
commands::clone_repo,
|
|
commands::github_device_flow_start,
|
|
commands::github_device_flow_poll,
|
|
commands::github_get_user,
|
|
commands::search_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
|
|
])
|
|
.build(tauri::generate_context!())
|
|
.expect("error while building tauri application")
|
|
.run(|_app_handle, _event| {
|
|
#[cfg(desktop)]
|
|
{
|
|
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");
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|