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)
| 1200 | /// The system fields are always included to enable round-trip (deflation |
| 1201 | /// can reconstruct the VaultEntry from the file). |
| 1202 | fn render_entry_to_markdown(entry: &VaultEntry) -> String { |
| 1203 | let mut output = String::new(); |
| 1204 | output.push_str("---\n"); |
| 1205 | |
| 1206 | // Parse the frontmatter JSON into a map so we can merge system fields |
| 1207 | let mut fm: serde_json::Map<String, serde_json::Value> = |
| 1208 | serde_json::from_str(&entry.frontmatter_json).unwrap_or_default(); |
| 1209 | |
| 1210 | // Add system fields (these take precedence) |
| 1211 | fm.insert( |
| 1212 | "entry_type".to_string(), |
| 1213 | serde_json::Value::String(entry.entry_type.to_string()), |
| 1214 | ); |
| 1215 | fm.insert( |
| 1216 | "content_hash".to_string(), |
| 1217 | serde_json::Value::String(Hash::from_bytes(entry.content_hash).to_base32()), |
| 1218 | ); |
| 1219 | fm.insert( |
| 1220 | "created_at".to_string(), |
| 1221 | serde_json::Value::String(entry.created_at.clone()), |
| 1222 | ); |
| 1223 | fm.insert( |
| 1224 | "updated_at".to_string(), |
| 1225 | serde_json::Value::String(entry.updated_at.clone()), |
| 1226 | ); |
| 1227 | |
| 1228 | // Write frontmatter in a stable order: user fields first (sorted), then system fields |
| 1229 | let system_keys = ["entry_type", "content_hash", "created_at", "updated_at"]; |
| 1230 | let mut user_keys: Vec<&String> = fm |
| 1231 | .keys() |
| 1232 | .filter(|k| !system_keys.contains(&k.as_str())) |
| 1233 | .collect(); |
| 1234 | user_keys.sort(); |
| 1235 | |
| 1236 | // Write user fields |
| 1237 | for key in &user_keys { |
| 1238 | write_frontmatter_field(&mut output, key, &fm[key.as_str()]); |
| 1239 | } |
| 1240 | |
| 1241 | // Write system fields |
| 1242 | for key in &system_keys { |
| 1243 | if let Some(value) = fm.get(*key) { |
| 1244 | write_frontmatter_field(&mut output, key, value); |
| 1245 | } |
| 1246 | } |
| 1247 | |
| 1248 | output.push_str("---\n"); |
| 1249 | |
| 1250 | // Content body |
| 1251 | let content = String::from_utf8_lossy(&entry.content_bytes); |
| 1252 | if !content.is_empty() { |
| 1253 | output.push_str(&content); |
| 1254 | // Ensure file ends with a newline |
| 1255 | if !content.ends_with('\n') { |
| 1256 | output.push('\n'); |
| 1257 | } |
| 1258 | } |
| 1259 |