Render a vault entry to a markdown string with YAML frontmatter. The frontmatter is constructed from: 1. The entry's `frontmatter_json` fields (user-provided metadata) 2. System metadata (entry_type, content_hash, created_at, updated_at) The system fields are always included to enable round-trip (deflation can reconstruct the VaultEntry from the file).
(entry: &VaultEntry)
| 961 | /// The system fields are always included to enable round-trip (deflation |
| 962 | /// can reconstruct the VaultEntry from the file). |
| 963 | fn render_entry_to_markdown(entry: &VaultEntry) -> String { |
| 964 | let mut output = String::new(); |
| 965 | output.push_str("---\n"); |
| 966 | |
| 967 | // Parse the frontmatter JSON into a map so we can merge system fields |
| 968 | let mut fm: serde_json::Map<String, serde_json::Value> = |
| 969 | serde_json::from_str(&entry.frontmatter_json).unwrap_or_default(); |
| 970 | |
| 971 | // Add system fields (these take precedence) |
| 972 | fm.insert( |
| 973 | "entry_type".to_string(), |
| 974 | serde_json::Value::String(entry.entry_type.to_string()), |
| 975 | ); |
| 976 | fm.insert( |
| 977 | "content_hash".to_string(), |
| 978 | serde_json::Value::String(Hash::from_bytes(entry.content_hash).to_base32()), |
| 979 | ); |
| 980 | fm.insert( |
| 981 | "created_at".to_string(), |
| 982 | serde_json::Value::String(entry.created_at.clone()), |
| 983 | ); |
| 984 | fm.insert( |
| 985 | "updated_at".to_string(), |
| 986 | serde_json::Value::String(entry.updated_at.clone()), |
| 987 | ); |
| 988 | |
| 989 | // Write frontmatter in a stable order: user fields first (sorted), then system fields |
| 990 | let system_keys = ["entry_type", "content_hash", "created_at", "updated_at"]; |
| 991 | let mut user_keys: Vec<&String> = fm |
| 992 | .keys() |
| 993 | .filter(|k| !system_keys.contains(&k.as_str())) |
| 994 | .collect(); |
| 995 | user_keys.sort(); |
| 996 | |
| 997 | // Write user fields |
| 998 | for key in &user_keys { |
| 999 | write_frontmatter_field(&mut output, key, &fm[key.as_str()]); |
| 1000 | } |
| 1001 | |
| 1002 | // Write system fields |
| 1003 | for key in &system_keys { |
| 1004 | if let Some(value) = fm.get(*key) { |
| 1005 | write_frontmatter_field(&mut output, key, value); |
| 1006 | } |
| 1007 | } |
| 1008 | |
| 1009 | output.push_str("---\n"); |
| 1010 | |
| 1011 | // Content body |
| 1012 | let content = String::from_utf8_lossy(&entry.content_bytes); |
| 1013 | if !content.is_empty() { |
| 1014 | output.push_str(&content); |
| 1015 | // Ensure file ends with a newline |
| 1016 | if !content.ends_with('\n') { |
| 1017 | output.push('\n'); |
| 1018 | } |
| 1019 | } |
| 1020 |