znote 1.0.0

A lightweight desktop editor for Typst with live preview, built with Tauri
mod agent;
mod compile;
mod export;
mod extract;
mod knowledge;
mod models;
mod policy;
mod privacy;
mod project;
mod sealed;
mod snapshot;
mod theme;
mod workspace;

use std::path::Path;

use tauri::{Emitter, Manager};

pub async fn blocking<T: Send + 'static>(
    work: impl FnOnce() -> Result<T, String> + Send + 'static,
) -> Result<T, String> {
    tauri::async_runtime::spawn_blocking(work)
        .await
        .map_err(|_| "This could not be finished. Try again.".to_string())?
}

pub fn guarded<T>(failure: &str, work: impl FnOnce() -> Result<T, String>) -> Result<T, String> {
    std::panic::catch_unwind(std::panic::AssertUnwindSafe(work))
        .unwrap_or_else(|_| Err(failure.to_string()))
}

#[tauri::command]
fn app_version() -> Result<String, String> {
    Ok(env!("CARGO_PKG_VERSION").to_string())
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .plugin(tauri_plugin_single_instance::init(
            |app, arguments, folder| {
                if let Some(file) = project::asked(&arguments, Path::new(&folder)) {
                    let _ = app.emit("project-asked", file);
                }
                if let Some(window) = app.get_webview_window("main") {
                    let _ = window.unminimize();
                    let _ = window.set_focus();
                }
            },
        ))
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_store::Builder::default().build())
        .manage(compile::Compiler::default())
        .manage(workspace::Workspace::default())
        .manage(export::Exports::default())
        .manage(agent::Agent::default())
        .manage(project::Project::default())
        .manage(knowledge::Knowledge::default())
        .invoke_handler(tauri::generate_handler![
            app_version,
            compile::compile,
            workspace::pick_typst_file,
            workspace::open_workspace,
            workspace::list_tree,
            workspace::create_entry,
            workspace::move_entry,
            workspace::delete_entry,
            workspace::read_page,
            workspace::search_pages,
            workspace::write_page,
            theme::list_themes,
            theme::import_theme,
            theme::store_themes,
            theme::install_store_theme,
            theme::remove_theme,
            project::save_snapshot,
            project::project_asked,
            project::project_open,
            project::project_new,
            project::project_unlock,
            project::project_note,
            export::export_page,
            export::open_export,
            export::show_export,
            agent::agent_status,
            agent::agent_install,
            agent::agent_connect,
            agent::agent_code,
            agent::agent_disconnect,
            agent::agent_cancel,
            agent::agent_run,
            agent::agent_attach,
            agent::agent_preview,
            agent::agent_files,
            agent::agent_level,
            agent::agent_in_scope,
            agent::agent_forget,
            agent::provider_connect,
            agent::provider_models,
            agent::chats_list,
            agent::chat_save,
            knowledge::privacy_options,
            policy::privacy_policy,
            knowledge::privacy_pages,
            knowledge::privacy_view,
            knowledge::privacy_override,
            knowledge::privacy_overrides,
            knowledge::privacy_forget,
            knowledge::knowledge_status,
            knowledge::knowledge_create,
            knowledge::knowledge_pick,
            knowledge::knowledge_import,
            knowledge::knowledge_unlock,
            knowledge::knowledge_activate,
            knowledge::knowledge_export,
            knowledge::knowledge_password,
            knowledge::knowledge_remove,
            knowledge::knowledge_sync,
            knowledge::knowledge_add,
            knowledge::knowledge_attach,
            knowledge::knowledge_forget,
            knowledge::knowledge_rebuild,
            knowledge::glossary_import,
            models::models_download,
            models::models_cancel,
        ])
        .on_window_event(|window, event| {
            if let tauri::WindowEvent::Focused(false) = event {
                let app = window.app_handle().clone();
                std::thread::spawn(move || project::save(&app));
            }
        })
        .build(tauri::generate_context!())
        .expect("error while running tauri application")
        .run(|app, event| {
            if let tauri::RunEvent::Exit = event {
                app.state::<agent::Agent>().cancel_all();
                project::close(app);
                agent::clear_mirrors(app);
            }
        });
}

#[cfg(test)]
mod tests {
    #[test]
    fn a_panic_in_guarded_work_becomes_a_message() {
        let failed: Result<(), String> = super::guarded("It broke.", || panic!("bad input"));
        assert_eq!(failed, Err("It broke.".into()));
        assert_eq!(super::guarded("It broke.", || Ok(7)), Ok(7));
    }
}