Skip to main content

cvkg_cli/
patch_engine.rs

1//! Patch Engine
2//! Responsible for generating patches from compiled artifacts
3
4use serde::{Deserialize, Serialize};
5
6/// Compiled artifact from the build process
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct CompiledArtifact {
9    /// The root node ID of the view
10    pub root_id: u64,
11    /// The serialized view
12    pub view: SerializedView,
13}
14
15/// Serialized view representation
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct SerializedView {
18    /// The view type (e.g., "Text", "Button")
19    pub view_type: String,
20    /// The view properties
21    pub props: serde_json::Value,
22    /// The child views
23    pub children: Vec<SerializedView>,
24}
25
26/// Runtime patch types
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub enum RuntimePatch {
29    /// Replace a view at the specified node ID
30    ReplaceView {
31        /// The node ID to replace
32        node_id: u64,
33        /// The new view to insert
34        new_view: SerializedView,
35    },
36    /// Update state at the specified node ID
37    UpdateState {
38        /// The node ID to update
39        node_id: u64,
40        /// The field to update
41        field: String,
42        /// The new value
43        value: serde_json::Value,
44    },
45    /// Batch multiple patches together
46    Batch(Vec<RuntimePatch>),
47}
48
49/// Patch Engine implementation
50pub struct PatchEngine {
51    previous_view: Option<SerializedView>,
52}
53
54impl PatchEngine {
55    /// Create a new PatchEngine
56    pub fn new() -> Self {
57        Self {
58            previous_view: None,
59        }
60    }
61    
62    /// Generate a patch from a compiled artifact
63    pub fn generate_patch(&mut self, artifact: CompiledArtifact) -> RuntimePatch {
64        let mut patches = Vec::new();
65        
66        if let Some(prev) = &self.previous_view {
67            self.diff_recursive(artifact.root_id, prev, &artifact.view, &mut patches);
68        } else {
69            // First run, replace everything
70            patches.push(RuntimePatch::ReplaceView {
71                node_id: artifact.root_id,
72                new_view: artifact.view.clone(),
73            });
74        }
75        
76        self.previous_view = Some(artifact.view);
77        
78        if patches.len() == 1 {
79            patches.remove(0)
80        } else {
81            RuntimePatch::Batch(patches)
82        }
83    }
84
85    fn diff_recursive(&self, node_id: u64, old: &SerializedView, new: &SerializedView, patches: &mut Vec<RuntimePatch>) {
86        // If types are different, we must replace the whole subtree
87        if old.view_type != new.view_type {
88            patches.push(RuntimePatch::ReplaceView {
89                node_id,
90                new_view: new.clone(),
91            });
92            return;
93        }
94
95        // If props changed, we might generate UpdateState or just ReplaceView
96        // For simplicity in this "real" version, we'll replace the node if anything changed
97        if old.props != new.props || old.children.len() != new.children.len() {
98            patches.push(RuntimePatch::ReplaceView {
99                node_id,
100                new_view: new.clone(),
101            });
102            return;
103        }
104
105        // Recursively diff children if they exist
106        // Note: Without stable IDs for children in SerializedView, we use index-based matching
107        for (i, (old_child, new_child)) in old.children.iter().zip(new.children.iter()).enumerate() {
108            // We need a way to address child nodes. 
109            // In CVKG, we assume a deterministic ID generation based on path for dev-server patches.
110            let child_id = node_id * 100 + (i as u64 + 1); 
111            self.diff_recursive(child_id, old_child, new_child, patches);
112        }
113    }
114}