metis_core/domain/documents/vision/
mod.rs

1use super::content::DocumentContent;
2use super::helpers::FrontmatterParser;
3use super::metadata::DocumentMetadata;
4use super::traits::{Document, DocumentTemplate, DocumentValidationError};
5use super::types::{DocumentId, DocumentType, Phase, Tag};
6use gray_matter;
7use std::path::Path;
8use tera::{Context, Tera};
9
10/// A Vision document represents the high-level direction and goals
11#[derive(Debug)]
12pub struct Vision {
13    core: super::traits::DocumentCore,
14}
15
16impl Vision {
17    /// Create a new Vision document with content rendered from template
18    pub fn new(
19        title: String,
20        tags: Vec<Tag>,
21        archived: bool,
22    ) -> Result<Self, DocumentValidationError> {
23        // Create fresh metadata
24        let metadata = DocumentMetadata::new();
25
26        // Render the content template
27        let template_content = include_str!("content.md");
28        let mut tera = Tera::default();
29        tera.add_raw_template("vision_content", template_content)
30            .map_err(|e| {
31                DocumentValidationError::InvalidContent(format!("Template error: {}", e))
32            })?;
33
34        let mut context = Context::new();
35        context.insert("title", &title);
36
37        let rendered_content = tera.render("vision_content", &context).map_err(|e| {
38            DocumentValidationError::InvalidContent(format!("Template render error: {}", e))
39        })?;
40
41        let content = DocumentContent::new(&rendered_content);
42
43        Ok(Self {
44            core: super::traits::DocumentCore {
45                title,
46                metadata,
47                content,
48                parent_id: None,        // Visions have no parents
49                blocked_by: Vec::new(), // Visions cannot be blocked
50                tags,
51                archived,
52            },
53        })
54    }
55
56    /// Create a Vision document from existing data (used when loading from file)
57    pub fn from_parts(
58        title: String,
59        metadata: DocumentMetadata,
60        content: DocumentContent,
61        tags: Vec<Tag>,
62        archived: bool,
63    ) -> Self {
64        Self {
65            core: super::traits::DocumentCore {
66                title,
67                metadata,
68                content,
69                parent_id: None,        // Visions have no parents
70                blocked_by: Vec::new(), // Visions cannot be blocked
71                tags,
72                archived,
73            },
74        }
75    }
76
77    /// Create a Vision document by reading and parsing a file
78    pub async fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, DocumentValidationError> {
79        let raw_content = std::fs::read_to_string(path.as_ref()).map_err(|e| {
80            DocumentValidationError::InvalidContent(format!("Failed to read file: {}", e))
81        })?;
82
83        Self::from_content(&raw_content)
84    }
85
86    /// Create a Vision document from raw file content string
87    pub fn from_content(raw_content: &str) -> Result<Self, DocumentValidationError> {
88        // Parse frontmatter and content
89        let parsed = gray_matter::Matter::<gray_matter::engine::YAML>::new().parse(raw_content);
90
91        // Extract frontmatter data
92        let frontmatter = parsed.data.ok_or_else(|| {
93            DocumentValidationError::MissingRequiredField("frontmatter".to_string())
94        })?;
95
96        // Parse frontmatter into structured data
97        let fm_map = match frontmatter {
98            gray_matter::Pod::Hash(map) => map,
99            _ => {
100                return Err(DocumentValidationError::InvalidContent(
101                    "Frontmatter must be a hash/map".to_string(),
102                ))
103            }
104        };
105
106        // Extract required fields
107        let title = FrontmatterParser::extract_string(&fm_map, "title")?;
108        let archived = FrontmatterParser::extract_bool(&fm_map, "archived").unwrap_or(false);
109
110        // Parse timestamps
111        let created_at = FrontmatterParser::extract_datetime(&fm_map, "created_at")?;
112        let updated_at = FrontmatterParser::extract_datetime(&fm_map, "updated_at")?;
113        let exit_criteria_met =
114            FrontmatterParser::extract_bool(&fm_map, "exit_criteria_met").unwrap_or(false);
115
116        // Parse tags
117        let tags = FrontmatterParser::extract_tags(&fm_map)?;
118
119        // Verify this is actually a vision document
120        let level = FrontmatterParser::extract_string(&fm_map, "level")?;
121        if level != "vision" {
122            return Err(DocumentValidationError::InvalidContent(format!(
123                "Expected level 'vision', found '{}'",
124                level
125            )));
126        }
127
128        // Create metadata and content
129        let metadata =
130            DocumentMetadata::from_frontmatter(created_at, updated_at, exit_criteria_met);
131        let content = DocumentContent::from_markdown(&parsed.content);
132
133        Ok(Self::from_parts(title, metadata, content, tags, archived))
134    }
135
136    /// Get the next phase in the Vision sequence
137    fn next_phase_in_sequence(current: Phase) -> Option<Phase> {
138        use Phase::*;
139        match current {
140            Draft => Some(Review),
141            Review => Some(Published),
142            Published => None, // Final phase
143            _ => None,         // Invalid phase for Vision
144        }
145    }
146
147    /// Update the phase tag in the document's tags
148    fn update_phase_tag(&mut self, new_phase: Phase) {
149        // Remove any existing phase tags
150        self.core.tags.retain(|tag| !matches!(tag, Tag::Phase(_)));
151        // Add the new phase tag
152        self.core.tags.push(Tag::Phase(new_phase));
153        // Update timestamp
154        self.core.metadata.updated_at = chrono::Utc::now();
155    }
156
157    /// Write the Vision document to a file
158    pub async fn to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), DocumentValidationError> {
159        let content = self.to_content()?;
160        std::fs::write(path.as_ref(), content).map_err(|e| {
161            DocumentValidationError::InvalidContent(format!("Failed to write file: {}", e))
162        })
163    }
164
165    /// Convert the Vision document to its markdown string representation using templates
166    pub fn to_content(&self) -> Result<String, DocumentValidationError> {
167        let mut tera = Tera::default();
168
169        // Add the frontmatter template to Tera
170        tera.add_raw_template("frontmatter", self.frontmatter_template())
171            .map_err(|e| {
172                DocumentValidationError::InvalidContent(format!("Template error: {}", e))
173            })?;
174
175        // Create context with all document data
176        let mut context = Context::new();
177        context.insert("slug", &self.id().to_string());
178        context.insert("title", self.title());
179        context.insert("created_at", &self.metadata().created_at.to_rfc3339());
180        context.insert("updated_at", &self.metadata().updated_at.to_rfc3339());
181        context.insert("archived", &self.archived().to_string());
182        context.insert(
183            "exit_criteria_met",
184            &self.metadata().exit_criteria_met.to_string(),
185        );
186
187        // Convert tags to strings
188        let tag_strings: Vec<String> = self.tags().iter().map(|tag| tag.to_str()).collect();
189        context.insert("tags", &tag_strings);
190
191        // Render frontmatter
192        let frontmatter = tera.render("frontmatter", &context).map_err(|e| {
193            DocumentValidationError::InvalidContent(format!("Frontmatter render error: {}", e))
194        })?;
195
196        // Use the actual content body
197        let content_body = &self.content().body;
198
199        // Use actual acceptance criteria if present, otherwise empty string
200        let acceptance_criteria = if let Some(ac) = &self.content().acceptance_criteria {
201            format!("\n\n## Acceptance Criteria\n\n{}", ac)
202        } else {
203            String::new()
204        };
205
206        // Combine everything
207        Ok(format!(
208            "---\n{}\n---\n\n{}{}",
209            frontmatter.trim_end(),
210            content_body,
211            acceptance_criteria
212        ))
213    }
214}
215
216impl Document for Vision {
217    // id() is provided by the trait with default implementation
218
219    fn document_type(&self) -> DocumentType {
220        DocumentType::Vision
221    }
222
223    fn title(&self) -> &str {
224        &self.core.title
225    }
226
227    fn metadata(&self) -> &DocumentMetadata {
228        &self.core.metadata
229    }
230
231    fn content(&self) -> &DocumentContent {
232        &self.core.content
233    }
234
235    fn core(&self) -> &super::traits::DocumentCore {
236        &self.core
237    }
238
239    fn can_transition_to(&self, phase: Phase) -> bool {
240        if let Ok(current_phase) = self.phase() {
241            use Phase::*;
242            matches!(
243                (current_phase, phase),
244                (Draft, Review) | (Review, Published)
245            )
246        } else {
247            false // Can't transition if we can't determine current phase
248        }
249    }
250
251    fn parent_id(&self) -> Option<&DocumentId> {
252        None // Visions never have parents
253    }
254
255    fn blocked_by(&self) -> &[DocumentId] {
256        &[] // Visions can never be blocked
257    }
258
259    fn validate(&self) -> Result<(), DocumentValidationError> {
260        // Vision-specific validation rules
261        if self.title().trim().is_empty() {
262            return Err(DocumentValidationError::InvalidTitle(
263                "Vision title cannot be empty".to_string(),
264            ));
265        }
266
267        if self.parent_id().is_some() {
268            return Err(DocumentValidationError::InvalidParent(
269                "Visions cannot have parents".to_string(),
270            ));
271        }
272
273        if !self.blocked_by().is_empty() {
274            return Err(DocumentValidationError::InvalidContent(
275                "Visions cannot be blocked by other documents".to_string(),
276            ));
277        }
278
279        Ok(())
280    }
281
282    fn exit_criteria_met(&self) -> bool {
283        // Check if all acceptance criteria checkboxes are checked
284        // This would typically parse the content for checkbox completion
285        // For now, return false as a placeholder
286        false
287    }
288
289    fn template(&self) -> DocumentTemplate {
290        DocumentTemplate {
291            frontmatter: self.frontmatter_template(),
292            content: self.content_template(),
293            acceptance_criteria: self.acceptance_criteria_template(),
294            file_extension: "md",
295        }
296    }
297
298    fn frontmatter_template(&self) -> &'static str {
299        include_str!("frontmatter.yaml")
300    }
301
302    fn content_template(&self) -> &'static str {
303        include_str!("content.md")
304    }
305
306    fn acceptance_criteria_template(&self) -> &'static str {
307        include_str!("acceptance_criteria.md")
308    }
309
310    fn transition_phase(
311        &mut self,
312        target_phase: Option<Phase>,
313    ) -> Result<Phase, DocumentValidationError> {
314        let current_phase = self.phase()?;
315
316        let new_phase = match target_phase {
317            Some(phase) => {
318                // Validate the transition is allowed
319                if !self.can_transition_to(phase) {
320                    return Err(DocumentValidationError::InvalidPhaseTransition {
321                        from: current_phase,
322                        to: phase,
323                    });
324                }
325                phase
326            }
327            None => {
328                // Auto-transition to next phase in sequence
329                match Self::next_phase_in_sequence(current_phase) {
330                    Some(next) => next,
331                    None => return Ok(current_phase), // Already at final phase
332                }
333            }
334        };
335
336        self.update_phase_tag(new_phase);
337        Ok(new_phase)
338    }
339
340    fn core_mut(&mut self) -> &mut super::traits::DocumentCore {
341        &mut self.core
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use crate::domain::documents::traits::DocumentValidationError;
349    use tempfile::tempdir;
350
351    #[tokio::test]
352    async fn test_vision_from_content() {
353        let content = r##"---
354id: test-vision
355level: vision
356title: "Test Vision"
357created_at: 2025-01-01T00:00:00Z
358updated_at: 2025-01-01T00:00:00Z
359archived: false
360
361tags:
362  - "#vision"
363  - "#phase/draft"
364
365exit_criteria_met: false
366---
367
368# Test Vision
369
370## Purpose
371
372This is a test vision for our system.
373
374## Current State
375
376We are here.
377
378## Future State
379
380We want to be there.
381
382## Acceptance Criteria
383
384- [ ] Purpose is clearly defined
385- [ ] Current and future states are documented
386"##;
387
388        let vision = Vision::from_content(content).unwrap();
389
390        assert_eq!(vision.title(), "Test Vision");
391        assert_eq!(vision.document_type(), DocumentType::Vision);
392        assert!(!vision.archived());
393        assert_eq!(vision.tags().len(), 2);
394        assert_eq!(vision.phase().unwrap(), Phase::Draft);
395        assert!(vision.content().has_acceptance_criteria());
396
397        // Round-trip test: write to file and read back
398        let temp_dir = tempdir().unwrap();
399        let file_path = temp_dir.path().join("test-vision.md");
400
401        vision.to_file(&file_path).await.unwrap();
402        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
403
404        assert_eq!(loaded_vision.title(), vision.title());
405        assert_eq!(loaded_vision.phase().unwrap(), vision.phase().unwrap());
406        assert_eq!(loaded_vision.content().body, vision.content().body);
407        assert_eq!(loaded_vision.archived(), vision.archived());
408        assert_eq!(loaded_vision.tags().len(), vision.tags().len());
409    }
410
411    #[test]
412    fn test_vision_invalid_level() {
413        let content = r##"---
414id: test-doc
415level: strategy
416title: "Test Strategy"
417created_at: 2025-01-01T00:00:00Z
418updated_at: 2025-01-01T00:00:00Z
419archived: false
420tags:
421  - "#strategy"
422  - "#phase/shaping"
423exit_criteria_met: false
424---
425
426# Test Strategy
427"##;
428
429        let result = Vision::from_content(content);
430        assert!(result.is_err());
431        match result.unwrap_err() {
432            DocumentValidationError::InvalidContent(msg) => {
433                assert!(msg.contains("Expected level 'vision'"));
434            }
435            _ => panic!("Expected InvalidContent error"),
436        }
437    }
438
439    #[test]
440    fn test_vision_missing_title() {
441        let content = r##"---
442id: test-vision
443level: vision
444created_at: 2025-01-01T00:00:00Z
445updated_at: 2025-01-01T00:00:00Z
446archived: false
447tags:
448  - "#vision"
449  - "#phase/draft"
450exit_criteria_met: false
451---
452
453Some content without a title in frontmatter.
454"##;
455
456        let result = Vision::from_content(content);
457        assert!(result.is_err());
458        match result.unwrap_err() {
459            DocumentValidationError::MissingRequiredField(field) => {
460                assert_eq!(field, "title");
461            }
462            _ => panic!("Expected MissingRequiredField error"),
463        }
464    }
465
466    #[tokio::test]
467    async fn test_vision_tag_parsing() {
468        let content = r##"---
469id: test-vision
470level: vision
471title: "Test Vision"
472created_at: 2025-01-01T00:00:00Z
473updated_at: 2025-01-01T00:00:00Z
474archived: false
475tags:
476  - "#vision"
477  - "#phase/review"
478  - "#high-priority"
479  - "urgent"
480exit_criteria_met: false
481---
482
483# Test Vision
484"##;
485
486        let vision = Vision::from_content(content).unwrap();
487        let tags = vision.tags();
488
489        assert_eq!(tags.len(), 4);
490        assert!(tags.contains(&Tag::Label("vision".to_string())));
491        assert!(tags.contains(&Tag::Phase(Phase::Review)));
492        assert!(tags.contains(&Tag::Label("high-priority".to_string())));
493        assert!(tags.contains(&Tag::Label("urgent".to_string())));
494
495        assert_eq!(vision.phase().unwrap(), Phase::Review);
496
497        // Round-trip test: write to file and read back
498        let temp_dir = tempdir().unwrap();
499        let file_path = temp_dir.path().join("test-vision.md");
500
501        vision.to_file(&file_path).await.unwrap();
502        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
503
504        assert_eq!(loaded_vision.title(), vision.title());
505        assert_eq!(loaded_vision.phase().unwrap(), vision.phase().unwrap());
506        assert_eq!(loaded_vision.tags().len(), vision.tags().len());
507
508        // Verify specific tags survived the round-trip
509        let loaded_tags = loaded_vision.tags();
510        assert!(loaded_tags.contains(&Tag::Label("vision".to_string())));
511        assert!(loaded_tags.contains(&Tag::Phase(Phase::Review)));
512        assert!(loaded_tags.contains(&Tag::Label("high-priority".to_string())));
513        assert!(loaded_tags.contains(&Tag::Label("urgent".to_string())));
514    }
515
516    #[tokio::test]
517    async fn test_vision_validation() {
518        let vision = Vision::new(
519            "Test Vision".to_string(),
520            vec![Tag::Label("vision".to_string()), Tag::Phase(Phase::Draft)],
521            false,
522        )
523        .expect("Failed to create vision");
524
525        assert!(vision.validate().is_ok());
526        assert_eq!(vision.parent_id(), None);
527        assert_eq!(vision.blocked_by().len(), 0);
528
529        // Round-trip test: write to file and read back
530        let temp_dir = tempdir().unwrap();
531        let file_path = temp_dir.path().join("test-vision.md");
532
533        // Write to file
534        vision.to_file(&file_path).await.unwrap();
535
536        // Read back from file
537        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
538
539        // Verify all fields match
540        assert_eq!(loaded_vision.title(), vision.title());
541        assert_eq!(loaded_vision.phase().unwrap(), vision.phase().unwrap());
542        assert_eq!(loaded_vision.content().body, vision.content().body);
543        assert_eq!(loaded_vision.archived(), vision.archived());
544        assert_eq!(loaded_vision.tags().len(), vision.tags().len());
545    }
546
547    #[tokio::test]
548    async fn test_vision_phase_transitions() {
549        let vision = Vision::new(
550            "Test Vision".to_string(),
551            vec![Tag::Phase(Phase::Draft)],
552            false,
553        )
554        .expect("Failed to create vision");
555
556        assert!(vision.can_transition_to(Phase::Review));
557        assert!(!vision.can_transition_to(Phase::Published));
558        assert!(!vision.can_transition_to(Phase::Active));
559
560        // Round-trip test: write to file and read back
561        let temp_dir = tempdir().unwrap();
562        let file_path = temp_dir.path().join("test-vision.md");
563
564        vision.to_file(&file_path).await.unwrap();
565        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
566
567        assert_eq!(loaded_vision.title(), vision.title());
568        assert_eq!(loaded_vision.phase().unwrap(), vision.phase().unwrap());
569        assert!(loaded_vision.can_transition_to(Phase::Review));
570        assert!(!loaded_vision.can_transition_to(Phase::Published));
571        assert!(!loaded_vision.can_transition_to(Phase::Active));
572    }
573
574    #[tokio::test]
575    async fn test_vision_transition_phase_auto() {
576        let mut vision = Vision::new(
577            "Test Vision".to_string(),
578            vec![Tag::Phase(Phase::Draft)],
579            false,
580        )
581        .expect("Failed to create vision");
582
583        // Auto-transition from Draft should go to Review
584        let new_phase = vision.transition_phase(None).unwrap();
585        assert_eq!(new_phase, Phase::Review);
586        assert_eq!(vision.phase().unwrap(), Phase::Review);
587
588        // Round-trip test after first transition
589        let temp_dir = tempdir().unwrap();
590        let file_path = temp_dir.path().join("test-vision.md");
591        vision.to_file(&file_path).await.unwrap();
592        let mut loaded_vision = Vision::from_file(&file_path).await.unwrap();
593        assert_eq!(loaded_vision.phase().unwrap(), Phase::Review);
594
595        // Auto-transition from Review should go to Published
596        let new_phase = loaded_vision.transition_phase(None).unwrap();
597        assert_eq!(new_phase, Phase::Published);
598        assert_eq!(loaded_vision.phase().unwrap(), Phase::Published);
599
600        // Round-trip test after second transition
601        loaded_vision.to_file(&file_path).await.unwrap();
602        let mut loaded_vision2 = Vision::from_file(&file_path).await.unwrap();
603        assert_eq!(loaded_vision2.phase().unwrap(), Phase::Published);
604
605        // Auto-transition from Published should stay at Published (final phase)
606        let new_phase = loaded_vision2.transition_phase(None).unwrap();
607        assert_eq!(new_phase, Phase::Published);
608        assert_eq!(loaded_vision2.phase().unwrap(), Phase::Published);
609
610        // Final round-trip test
611        loaded_vision2.to_file(&file_path).await.unwrap();
612        let loaded_vision3 = Vision::from_file(&file_path).await.unwrap();
613        assert_eq!(loaded_vision3.phase().unwrap(), Phase::Published);
614    }
615
616    #[tokio::test]
617    async fn test_vision_transition_phase_explicit() {
618        let mut vision = Vision::new(
619            "Test Vision".to_string(),
620            vec![Tag::Phase(Phase::Draft)],
621            false,
622        )
623        .expect("Failed to create vision");
624
625        // Explicit transition from Draft to Review
626        let new_phase = vision.transition_phase(Some(Phase::Review)).unwrap();
627        assert_eq!(new_phase, Phase::Review);
628        assert_eq!(vision.phase().unwrap(), Phase::Review);
629
630        // Round-trip test after first transition
631        let temp_dir = tempdir().unwrap();
632        let file_path = temp_dir.path().join("test-vision.md");
633        vision.to_file(&file_path).await.unwrap();
634        let mut loaded_vision = Vision::from_file(&file_path).await.unwrap();
635        assert_eq!(loaded_vision.phase().unwrap(), Phase::Review);
636
637        // Explicit transition from Review to Published
638        let new_phase = loaded_vision
639            .transition_phase(Some(Phase::Published))
640            .unwrap();
641        assert_eq!(new_phase, Phase::Published);
642        assert_eq!(loaded_vision.phase().unwrap(), Phase::Published);
643
644        // Final round-trip test
645        loaded_vision.to_file(&file_path).await.unwrap();
646        let loaded_vision2 = Vision::from_file(&file_path).await.unwrap();
647        assert_eq!(loaded_vision2.phase().unwrap(), Phase::Published);
648    }
649
650    #[tokio::test]
651    async fn test_vision_transition_phase_invalid() {
652        let mut vision = Vision::new(
653            "Test Vision".to_string(),
654            vec![Tag::Phase(Phase::Draft)],
655            false,
656        )
657        .expect("Failed to create vision");
658
659        // Invalid transition from Draft to Published (must go through Review)
660        let result = vision.transition_phase(Some(Phase::Published));
661        assert!(result.is_err());
662        match result.unwrap_err() {
663            DocumentValidationError::InvalidPhaseTransition { from, to } => {
664                assert_eq!(from, Phase::Draft);
665                assert_eq!(to, Phase::Published);
666            }
667            _ => panic!("Expected InvalidPhaseTransition error"),
668        }
669
670        // Should still be in Draft phase
671        assert_eq!(vision.phase().unwrap(), Phase::Draft);
672
673        // Round-trip test to ensure vision is still valid after failed transition
674        let temp_dir = tempdir().unwrap();
675        let file_path = temp_dir.path().join("test-vision.md");
676        vision.to_file(&file_path).await.unwrap();
677        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
678        assert_eq!(loaded_vision.phase().unwrap(), Phase::Draft);
679    }
680
681    #[tokio::test]
682    async fn test_vision_update_section() {
683        // First create a vision with the template
684        let mut vision = Vision::new(
685            "Test Vision".to_string(),
686            vec![Tag::Phase(Phase::Draft)],
687            false,
688        )
689        .expect("Failed to create vision");
690
691        // Then update its content to have specific test content
692        vision.core_mut().content = DocumentContent::new(
693            "## Purpose\n\nOriginal purpose\n\n## Current State\n\nOriginal state",
694        );
695
696        // Replace existing section
697        vision
698            .update_section("Updated purpose content", "Purpose", false)
699            .unwrap();
700        let content = vision.content().body.clone();
701        assert!(content.contains("## Purpose\n\nUpdated purpose content"));
702        assert!(!content.contains("Original purpose"));
703
704        // Append to existing section
705        vision
706            .update_section("Additional state info", "Current State", true)
707            .unwrap();
708        let content = vision.content().body.clone();
709        assert!(content.contains("Original state"));
710        assert!(content.contains("Additional state info"));
711
712        // Add new section
713        vision
714            .update_section("Future vision details", "Future State", false)
715            .unwrap();
716        let content = vision.content().body.clone();
717        assert!(content.contains("## Future State\n\nFuture vision details"));
718
719        // Round-trip test to ensure all updates persist
720        let temp_dir = tempdir().unwrap();
721        let file_path = temp_dir.path().join("test-vision.md");
722        vision.to_file(&file_path).await.unwrap();
723        let loaded_vision = Vision::from_file(&file_path).await.unwrap();
724
725        let loaded_content = loaded_vision.content().body.clone();
726        assert!(loaded_content.contains("## Purpose\n\nUpdated purpose content"));
727        assert!(loaded_content.contains("Original state"));
728        assert!(loaded_content.contains("Additional state info"));
729        assert!(loaded_content.contains("## Future State\n\nFuture vision details"));
730    }
731}