tawasuyu

tawasuyu/brahman

Rust 202TOML 61JSON 7HTML 12471 Sutichaykuna

feat(tahuantinsuyu): persistir flex de los splitters entre sesiones

Hasta ahora cada boot reseteaba los splitters al default (1:4
horizontal, 4:1 vertical), forzando a rearrastrar manualmente cada
vez. Ahora el flex se guarda en la tabla `settings` ya existente.

- `tahuantinsuyu-store`: nuevos `get_setting`/`set_setting` con
  upsert + test de roundtrip.
- `tahuantinsuyu` shell: al boot, `load_split_flex` lee
  `layout.main_split` y `layout.outer_split` (formato "a,b" como
  texto). Si no hay entry o está corrupto cae a defaults.
- Subscribe a `SplitEvent::DragEnd` en cada splitter — `save_split_flex`
  escribe los flex actuales al settings. Mouseup-driven, no
  cada-frame: 0 escrituras durante el drag, 1 al final.

`module_configs` ya estaba persistido por carta vía la tabla
`module_state` (`persist_module` + `load_persisted_module_states`),
no requiere cambios.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

e044d475167b0b210a79df049541c22d61edd4e2 · ruwaq sergio · (5 killa ñawpaq) · tayta 904f33406 · kay commitpi sach'ata qhaway

Semántico sayk'uy · 6

crates/apps/tahuantinsuyu/src/shell.rs

crates/modules/tahuantinsuyu/tahuantinsuyu-store/src/lib.rs

2 qillqakuna · +107 −7

diff --git a/crates/apps/tahuantinsuyu/src/shell.rs b/crates/apps/tahuantinsuyu/src/shell.rs
index 4623f5c..f135af4 100644
--- a/crates/apps/tahuantinsuyu/src/shell.rs
+++ b/crates/apps/tahuantinsuyu/src/shell.rs
@@ -42,7 +42,7 @@ use tahuantinsuyu_tree::{parse_city_atlas_tsv, TahuantinsuyuTree, TreeEvent};
 use yahweh_core::{LayoutDirection, NodeId};
 use yahweh_theme::Theme;
 use yahweh_widget_container_core::ChildSlot;
-use yahweh_widget_splitter::SplitContainer;
+use yahweh_widget_splitter::{SplitContainer, SplitEvent};
 use yahweh_widget_theme_switcher::theme_switcher;
 
 /// Status del broker brahman tal como lo vimos en el último ping.
@@ -125,20 +125,23 @@ impl Shell {
         })
         .detach();
 
-        // Splitter horizontal: tree + canvas (flex 1 : 4).
+        // Splitter horizontal: tree + canvas. Defaults (1.0, 4.0) salvo
+        // que tengamos un flex persistido en `settings`.
+        let (main_left, main_right) =
+            load_split_flex(&store, "layout.main_split", 1.0, 4.0);
         let main_split = cx.new(|cx| SplitContainer::new(LayoutDirection::Horizontal, cx));
         main_split.update(cx, |sc, cx| {
             sc.set_children(
                 vec![
                     ChildSlot {
                         id: NodeId::new("tts-tree"),
-                        flex: 1.0,
+                        flex: main_left,
                         label: None,
                         view: gpui::AnyView::from(tree.clone()),
                     },
                     ChildSlot {
                         id: NodeId::new("tts-canvas"),
-                        flex: 4.0,
+                        flex: main_right,
                         label: None,
                         view: gpui::AnyView::from(canvas.clone()),
                     },
@@ -147,20 +150,22 @@ impl Shell {
             );
         });
 
-        // Splitter vertical: main_split arriba, panel abajo (flex 4 : 1).
+        // Splitter vertical: main arriba, panel abajo. Defaults (4.0, 1.0).
+        let (outer_top, outer_bottom) =
+            load_split_flex(&store, "layout.outer_split", 4.0, 1.0);
         let outer_split = cx.new(|cx| {
             let mut sc = SplitContainer::new(LayoutDirection::Vertical, cx);
             sc.set_children(
                 vec![
                     ChildSlot {
                         id: NodeId::new("tts-main"),
-                        flex: 4.0,
+                        flex: outer_top,
                         label: None,
                         view: gpui::AnyView::from(main_split.clone()),
                     },
                     ChildSlot {
                         id: NodeId::new("tts-panel"),
-                        flex: 1.0,
+                        flex: outer_bottom,
                         label: None,
                         view: gpui::AnyView::from(panel.clone()),
                     },
@@ -170,6 +175,23 @@ impl Shell {
             sc
         });
 
+        // Persistir flex en `DragEnd`. Capturamos el store por valor
+        // (Store es Clone — comparte el Arc<Mutex<Connection>>).
+        let store_main = store.clone();
+        cx.subscribe(&main_split, move |_, sc, ev: &SplitEvent, cx| {
+            if matches!(ev, SplitEvent::DragEnd) {
+                save_split_flex(&store_main, "layout.main_split", sc.read(cx));
+            }
+        })
+        .detach();
+        let store_outer = store.clone();
+        cx.subscribe(&outer_split, move |_, sc, ev: &SplitEvent, cx| {
+            if matches!(ev, SplitEvent::DragEnd) {
+                save_split_flex(&store_outer, "layout.outer_split", sc.read(cx));
+            }
+        })
+        .detach();
+
         let shell = Self {
             store,
             tree,
@@ -896,6 +918,38 @@ fn set_module_enabled(
     }
 }
 
+/// Lee del `settings` el flex de un splitter (formato "left,right"). Si
+/// no hay nada persistido o está corrupto, devuelve los defaults.
+fn load_split_flex(store: &Store, key: &str, default_a: f32, default_b: f32) -> (f32, f32) {
+    let Ok(Some(raw)) = store.get_setting(key) else {
+        return (default_a, default_b);
+    };
+    let mut parts = raw.split(',');
+    let a = parts.next().and_then(|s| s.trim().parse::<f32>().ok());
+    let b = parts.next().and_then(|s| s.trim().parse::<f32>().ok());
+    match (a, b) {
+        (Some(a), Some(b)) if a > 0.0 && b > 0.0 => (a, b),
+        _ => (default_a, default_b),
+    }
+}
+
+/// Persiste los flex actuales de un splitter de 2 children. Si tiene
+/// más children (en el futuro) sólo guarda los dos primeros — ajustar
+/// el formato si se necesita más.
+fn save_split_flex(store: &Store, key: &str, sc: &SplitContainer) {
+    let children = sc.children();
+    let Some((first, rest)) = children.split_first() else {
+        return;
+    };
+    let Some(second) = rest.first() else {
+        return;
+    };
+    let payload = format!("{:.4},{:.4}", first.flex, second.flex);
+    if let Err(e) = store.set_setting(key, &payload) {
+        eprintln!("[shell] save_split_flex {}: {}", key, e);
+    }
+}
+
 impl Render for Shell {
     fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let theme = Theme::global(cx).clone();
diff --git a/crates/modules/tahuantinsuyu/tahuantinsuyu-store/src/lib.rs b/crates/modules/tahuantinsuyu/tahuantinsuyu-store/src/lib.rs
index 6742cd9..e93835a 100644
--- a/crates/modules/tahuantinsuyu/tahuantinsuyu-store/src/lib.rs
+++ b/crates/modules/tahuantinsuyu/tahuantinsuyu-store/src/lib.rs
@@ -419,6 +419,35 @@ impl Store {
         Ok(out)
     }
 
+    // -----------------------------------------------------------------
+    // Settings (key/value libre — layout, last-opened chart, etc.)
+    // -----------------------------------------------------------------
+
+    /// Lee un valor de la tabla `settings`. `None` si no existe.
+    pub fn get_setting(&self, key: &str) -> StoreResult<Option<String>> {
+        let conn = self.conn.lock().unwrap();
+        let val = conn
+            .query_row(
+                "SELECT value FROM settings WHERE key = ?1",
+                params![key],
+                |row| row.get::<_, String>(0),
+            )
+            .optional()?;
+        Ok(val)
+    }
+
+    /// Upsert un setting. El valor es texto libre — para JSON, el caller
+    /// serializa antes de llamar.
+    pub fn set_setting(&self, key: &str, value: &str) -> StoreResult<()> {
+        let conn = self.conn.lock().unwrap();
+        conn.execute(
+            "INSERT INTO settings (key, value) VALUES (?1, ?2) \
+             ON CONFLICT(key) DO UPDATE SET value = excluded.value",
+            params![key, value],
+        )?;
+        Ok(())
+    }
+
     // -----------------------------------------------------------------
     // Recursive descent: charts under a group/contact (para thumbnails)
     // -----------------------------------------------------------------
@@ -676,6 +705,23 @@ mod tests {
         assert_eq!(by_id["transit"].enabled, false);
     }
 
+    #[test]
+    fn settings_upsert_and_read() {
+        let s = Store::in_memory().unwrap();
+        assert_eq!(s.get_setting("layout.outer").unwrap(), None);
+        s.set_setting("layout.outer", "4.0,1.0").unwrap();
+        assert_eq!(
+            s.get_setting("layout.outer").unwrap().as_deref(),
+            Some("4.0,1.0")
+        );
+        // Upsert — el segundo set sobreescribe.
+        s.set_setting("layout.outer", "3.5,1.5").unwrap();
+        assert_eq!(
+            s.get_setting("layout.outer").unwrap().as_deref(),
+            Some("3.5,1.5")
+        );
+    }
+
     #[test]
     fn full_hierarchy_roundtrip() {
         let s = Store::in_memory().unwrap();