tawasuyu/llimphi
Sovereign retained-mode GPU UI framework in Rust (wgpu+vello+taffy+parley), Elm loop, 40+ widgets.
1//! `verbos` — uso canónico de [`grafo_nav`]: cómo un dominio implementa
2//! [`GrafoNavegable`] y resuelve los verbos (*cercenar*, *conciliar*) sobre su
3//! propio modelo. Headless a propósito — certifica la semántica en texto, sin
4//! UI (los adaptadores reales lo copian y le enchufan `cockpit_grafo_view`).
5//!
6//! Correr: `cargo run -p grafo-nav --example verbos`
7
8use grafo_nav::{GrafoNavegable, Nodo, NodoId, Rol, Verbo};
9
10/// Un dominio sintético: una "historia" de nodos encadenados. En un dominio
11/// real esto sería `minga::GrafoCommits`, `ayni::Conversacion`, etc.
12struct Historia {
13 nodos: Vec<(NodoId, String)>,
14 aristas: Vec<(NodoId, NodoId)>,
15 activo: Option<NodoId>,
16}
17
18impl GrafoNavegable for Historia {
19 fn nodos(&self) -> Vec<Nodo> {
20 self.nodos
21 .iter()
22 .map(|(id, et)| {
23 let rol = if self.activo == Some(*id) { Rol::Activo } else { Rol::Normal };
24 Nodo::new(*id, et.clone(), rol)
25 })
26 .collect()
27 }
28
29 fn aristas(&self) -> Vec<(NodoId, NodoId)> {
30 self.aristas.clone()
31 }
32
33 /// La política de verbos del dominio: saltar siempre; restablecer/cercenar
34 /// requieren selección; conciliar sólo si el grafo bifurcó.
35 fn acciones(&self, seleccion: Option<NodoId>) -> Vec<Verbo> {
36 let mut v = vec![Verbo::Saltar];
37 if seleccion.is_some() {
38 v.push(Verbo::Restablecer);
39 v.push(Verbo::Cercenar);
40 }
41 if self.bifurcado() {
42 v.push(Verbo::Conciliar);
43 }
44 v
45 }
46}
47
48impl Historia {
49 /// *Cercenar*: poda un nodo y todo su cono descendente. En minga sería el gc
50 /// de inalcanzables; en un notebook, borrar la celda con sus dependientes.
51 fn cercenar(&mut self, id: NodoId) {
52 let cono = self.cono_descendente(id); // ya ordenado, incluye `id`
53 self.nodos.retain(|(n, _)| !cono.contains(n));
54 self.aristas
55 .retain(|(f, t)| !cono.contains(f) && !cono.contains(t));
56 if self.activo.map(|a| cono.contains(&a)).unwrap_or(false) {
57 self.activo = None;
58 }
59 }
60
61 /// *Conciliar*: si hay bifurcación, crea un nodo-merge que apunta desde todas
62 /// las cabezas — la fusión de minga, el merge de cabezas de ayni.
63 fn conciliar(&mut self, nuevo_id: NodoId, etiqueta: &str) {
64 let mut cabezas = self.cabezas();
65 cabezas.sort_unstable();
66 if cabezas.len() < 2 {
67 return;
68 }
69 self.nodos.push((nuevo_id, etiqueta.to_string()));
70 for c in cabezas {
71 self.aristas.push((c, nuevo_id));
72 }
73 self.activo = Some(nuevo_id);
74 }
75}
76
77fn resumen(h: &Historia, titulo: &str) {
78 let mut cabezas = h.cabezas();
79 cabezas.sort_unstable();
80 println!("── {titulo}");
81 println!(" nodos={} raíces={:?} cabezas={:?} bifurcado={}",
82 h.nodos.len(), h.raices(), cabezas, h.bifurcado());
83}
84
85fn main() {
86 // Diamante con una cabeza extra suelta:
87 // 1→2, 1→3, 2→4, 3→4 (diamante) + 1→5 (cabeza suelta)
88 let mut h = Historia {
89 nodos: vec![
90 (1, "raíz".into()),
91 (2, "rama A".into()),
92 (3, "rama B".into()),
93 (4, "merge previo".into()),
94 (5, "experimento".into()),
95 ],
96 aristas: vec![(1, 2), (1, 3), (2, 4), (3, 4), (1, 5)],
97 activo: Some(4),
98 };
99
100 resumen(&h, "inicial");
101 println!(" acciones(sel=Some(5)) = {:?}",
102 h.acciones(Some(5)).iter().map(|v| v.etiqueta()).collect::<Vec<_>>());
103
104 // Cercenar el experimento (nodo 5): desaparece esa cabeza suelta.
105 println!("\n>> Cercenar(5) — poda el nodo y su cono");
106 h.cercenar(5);
107 resumen(&h, "tras cercenar");
108 assert!(!h.nodos.iter().any(|(n, _)| *n == 5), "5 fue podado");
109 assert!(!h.bifurcado(), "ya no bifurca: única cabeza = 4");
110
111 // Forzar una bifurcación nueva y conciliarla.
112 println!("\n>> añadir una rama divergente y Conciliar");
113 h.nodos.push((6, "hotfix".into()));
114 h.aristas.push((1, 6)); // 6 cuelga de la raíz → segunda cabeza
115 resumen(&h, "con divergencia");
116 assert!(h.bifurcado(), "dos cabezas: 4 y 6");
117 h.conciliar(7, "conciliación");
118 resumen(&h, "tras conciliar");
119 assert!(!h.bifurcado(), "conciliado: única cabeza = 7");
120 assert_eq!(h.cabezas(), vec![7]);
121 assert_eq!(h.activo, Some(7));
122
123 println!("\nOK — verbos cercenar/conciliar coherentes con la estructura derivada.");
124}