Merge branch 'main' into main

This commit is contained in:
github-actions[bot]
2026-04-29 12:34:15 +00:00
committed by GitHub
10 changed files with 89 additions and 37 deletions

View File

@@ -149,8 +149,6 @@ pub async fn stream_ai_agent(
mod tests {
use super::*;
use crate::vault::AiGuidanceFileState;
use serde_json::Value;
use std::{fs, path::Path};
#[test]
fn guidance_commands_report_and_restore_vault_guidance_files() {
@@ -173,24 +171,4 @@ mod tests {
assert!(dir.path().join("CLAUDE.md").exists());
assert!(dir.path().join("GEMINI.md").exists());
}
#[test]
fn desktop_capability_allows_opening_restored_guidance_files() {
let capability_path = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("capabilities")
.join("default.json");
let capability_json = fs::read_to_string(capability_path).unwrap();
let capability: Value = serde_json::from_str(&capability_json).unwrap();
let permissions = capability
.get("permissions")
.and_then(Value::as_array)
.unwrap();
assert!(
permissions
.iter()
.any(|permission| permission.as_str() == Some("opener:allow-open-path")),
"desktop capabilities must allow opener open_path so restored guidance files can be opened"
);
}
}

View File

@@ -23,6 +23,14 @@ fn with_note_path<T>(
)
}
fn with_external_file_path<T>(
path: &Path,
vault_path: Option<&Path>,
action: impl FnOnce(&Path) -> Result<T, String>,
) -> Result<T, String> {
with_note_path(path, vault_path, ValidatedPathMode::Existing, action)
}
fn with_expanded_vault_root<T>(
path: &Path,
action: impl FnOnce(&Path) -> Result<T, String>,
@@ -75,6 +83,26 @@ pub fn sync_vault_asset_scope_for_window(
})
}
#[tauri::command]
pub fn open_vault_file_external(
app_handle: tauri::AppHandle,
path: PathBuf,
vault_path: Option<PathBuf>,
) -> Result<(), String> {
with_external_file_path(path.as_path(), vault_path.as_deref(), |validated_path| {
open_path_with_default_app(&app_handle, validated_path)
})
}
fn open_path_with_default_app(app_handle: &tauri::AppHandle, path: &Path) -> Result<(), String> {
use tauri_plugin_opener::OpenerExt;
app_handle
.opener()
.open_path(path.to_string_lossy().into_owned(), None::<String>)
.map_err(|error| error.to_string())
}
fn with_writable_note_path<T>(
path: PathBuf,
vault_path: Option<PathBuf>,
@@ -356,6 +384,41 @@ mod tests {
assert!(folder_error.contains("Path must stay inside the active vault"));
}
#[test]
fn external_file_paths_accept_files_inside_requested_vault() {
let dir = TempDir::new().unwrap();
let root = vault_root(&dir);
let attachment = note_path(&dir, "attachments/photo.png");
fs::create_dir_all(attachment.parent().unwrap()).unwrap();
fs::write(&attachment, "image-bytes").unwrap();
let validated = with_external_file_path(
attachment.as_path(),
Some(root.as_path()),
|validated_path| Ok(validated_path.to_path_buf()),
)
.unwrap();
assert_eq!(validated, attachment);
}
#[test]
fn external_file_paths_reject_files_outside_requested_vault() {
let vault = TempDir::new().unwrap();
let outside = TempDir::new().unwrap();
let outside_file = outside.path().join("photo.png");
fs::write(&outside_file, "image-bytes").unwrap();
let error = with_external_file_path(
outside_file.as_path(),
Some(vault.path()),
|validated_path| Ok(validated_path.to_path_buf()),
)
.unwrap_err();
assert!(error.contains("Path must stay inside the active vault"));
}
#[test]
fn validate_note_content_compares_against_disk() {
let dir = TempDir::new().unwrap();

View File

@@ -411,6 +411,7 @@ macro_rules! app_invoke_handler {
commands::reload_vault,
commands::reload_vault_entry,
commands::sync_vault_asset_scope_for_window,
commands::open_vault_file_external,
commands::sync_note_title,
commands::save_image,
commands::copy_image_to_vault,