feat(tahuantinsuyu): fase 2 — CRUD UX sobre el tree (menú, modales, form natal)

Right-click sobre el explorador izquierdo abre menú contextual cuyas
opciones dependen del target (raíz, group, contact o chart). Modales
flotantes para crear/renombrar usando yahweh-widget-text-input; un
form más completo de 11 campos para la birth data al crear cartas
natales. Borrar pide confirmación por window.prompt nativo.

- tahuantinsuyu-store: rename_contact, rename_chart, move_group,
  move_contact (los `move_*` para fase posterior de drag-to-nest).
- tahuantinsuyu-tree: estado interno (Menu, Modal enum, ChartForm),
  handlers de ContextMenuRequested, render overlays.
  Soporta seis modales: rename de g/c/h, create group/contact, form
  natal completo con parseo + reporte de errores inline.
  Auto-expande el contact tras crear una carta.
  Nuevo evento TreeEvent::HierarchyChanged tras cada mutación.
- shell: maneja HierarchyChanged sin propagar selección.

`cargo check` y `cargo test` verdes. Fase 3 viene con engine real
contra eternal-astrology + pintado de la rueda.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-16 01:13:17 +00:00
parent c48638fe87
commit bcb92b537e
5 changed files with 943 additions and 18 deletions
@@ -154,6 +154,19 @@ impl Store {
Ok(())
}
/// Cambia el `parent_id` de un Group. Pasar `None` para mover a raíz.
/// **No** valida ciclos — el caller debe garantizar que el nuevo
/// padre no sea descendiente del que mueve (sino la DB queda con un
/// ciclo que el list_groups no rompe pero hace al CTE infinito).
pub fn move_group(&self, id: GroupId, new_parent: Option<GroupId>) -> StoreResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE groups SET parent_id = ?2 WHERE id = ?1",
params![id.to_string(), new_parent.map(|g| g.to_string())],
)?;
Ok(())
}
// -----------------------------------------------------------------
// Contacts
// -----------------------------------------------------------------
@@ -204,6 +217,24 @@ impl Store {
Ok(())
}
pub fn rename_contact(&self, id: ContactId, name: &str) -> StoreResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE contacts SET name = ?2 WHERE id = ?1",
params![id.to_string(), name],
)?;
Ok(())
}
pub fn move_contact(&self, id: ContactId, new_group: Option<GroupId>) -> StoreResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE contacts SET group_id = ?2 WHERE id = ?1",
params![id.to_string(), new_group.map(|g| g.to_string())],
)?;
Ok(())
}
// -----------------------------------------------------------------
// Charts
// -----------------------------------------------------------------
@@ -285,6 +316,15 @@ impl Store {
Ok(())
}
pub fn rename_chart(&self, id: ChartId, label: &str) -> StoreResult<()> {
let conn = self.conn.lock().unwrap();
conn.execute(
"UPDATE charts SET label = ?2 WHERE id = ?1",
params![id.to_string(), label],
)?;
Ok(())
}
// -----------------------------------------------------------------
// Module state
// -----------------------------------------------------------------