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)
| 668 | /// The system fields are always included to enable round-trip (deflation |
| 669 | /// can reconstruct the VaultEntry from the file). |
| 670 | fn render_entry_to_markdown(entry: &VaultEntry) -> String { |
| 671 | let mut output = String::new(); |
| 672 | output.push_str("---\n"); |
| 673 | |
| 674 | // Parse the frontmatter JSON into a map so we can merge system fields |
| 675 | let mut fm: serde_json::Map<String, serde_json::Value> = |
| 676 | serde_json::from_str(&entry.frontmatter_json).unwrap_or_default(); |
| 677 | |
| 678 | // Add system fields (these take precedence) |
| 679 | fm.insert( |
| 680 | "entry_type".to_string(), |
| 681 | serde_json::Value::String(entry.entry_type.to_string()), |
| 682 | ); |
| 683 | fm.insert( |
| 684 | "content_hash".to_string(), |
| 685 | serde_json::Value::String(Hash::from_bytes(entry.content_hash).to_base32()), |
| 686 | ); |
| 687 | fm.insert( |
| 688 | "created_at".to_string(), |
| 689 | serde_json::Value::String(entry.created_at.clone()), |
| 690 | ); |
| 691 | fm.insert( |
| 692 | "updated_at".to_string(), |
| 693 | serde_json::Value::String(entry.updated_at.clone()), |
| 694 | ); |
| 695 | |
| 696 | // Write frontmatter in a stable order: user fields first (sorted), then system fields |
| 697 | let system_keys = ["entry_type", "content_hash", "created_at", "updated_at"]; |
| 698 | let mut user_keys: Vec<&String> = fm |
| 699 | .keys() |
| 700 | .filter(|k| !system_keys.contains(&k.as_str())) |
| 701 | .collect(); |
| 702 | user_keys.sort(); |
| 703 | |
| 704 | // Write user fields |
| 705 | for key in &user_keys { |
| 706 | write_frontmatter_field(&mut output, key, &fm[key.as_str()]); |
| 707 | } |
| 708 | |
| 709 | // Write system fields |
| 710 | for key in &system_keys { |
| 711 | if let Some(value) = fm.get(*key) { |
| 712 | write_frontmatter_field(&mut output, key, value); |
| 713 | } |
| 714 | } |
| 715 | |
| 716 | output.push_str("---\n"); |
| 717 | |
| 718 | // Content body |
| 719 | let content = String::from_utf8_lossy(&entry.content_bytes); |
| 720 | if !content.is_empty() { |
| 721 | output.push_str(&content); |
| 722 | // Ensure file ends with a newline |
| 723 | if !content.ends_with('\n') { |
| 724 | output.push('\n'); |
| 725 | } |
| 726 | } |
| 727 |