feat(tahuantinsuyu): fase 17 — filtros de aspectos + editor + cleanup + labels
Fase completa con 4 mejoras independientes que reusan toda la
infraestructura previa:
## A — Filtros de aspectos en NatalModule
NatalModule gana 3 controles nuevos que SÍ recomponen (a diferencia
de los show_* que solo togglean visibilidad):
- Toggle "Mayores (☌ ☍ △ □ ⚹)" default true
- Toggle "Menores (quincunx, semi-…)" default false
- Slider "Multiplicador de orbe" range 0.25..2.5 step 0.25 default 1.0
Engine API extendida sin romper la existente:
- pub struct NatalOptions { show_majors, show_minors, orb_multiplier }
- pub fn compose_with_options(chart, offset, requests, &NatalOptions)
- compose() queda como wrapper con NatalOptions::default()
- bridge::compose acepta el natal_options, construye OrbTable escalada
(build_orb_table multiplier) y filtra aspects antes de pasarlos a
build_render_model. Build_render_model dejó de filtrar majors
internamente — ahora respeta lo que recibe.
Shell wire:
- build_natal_options() lee aspect_majors/aspect_minors/orb_multiplier
desde module_configs["natal"] con defaults seguros.
- on_panel_event para natal: si key empieza con "show_" → canvas
visibility (sin recompose); otherwise → update module_configs +
persist + render_current.
- render_current pasa natal_options a compose_with_options.
## B — Editor de carta natal existente
- Store::update_chart(id, label, &birth, &config) — actualiza tres
columnas preservando id/contact_id/related/created_at_ms y todo el
module_state asociado (la FK CASCADE no se dispara por UPDATE).
- Tree: Modal::EditChart { id, form, error } reusa ChartForm que ya
manejaba el create. open_edit_chart(id, w, cx) lee la carta con
store.get_chart, pre-carga cada TextInput con el valor existente
(label, birthplace, año, mes, día, hora, min, tz, lat, lon, alt).
submit_modal::EditChart lee form, llama update_chart, preserva el
config existente (zodiac/house_system/bodies no se editan acá).
Menú contextual del chart agrega "Editar…" entre "Abrir" y
"Renombrar".
- render_chart_form ahora toma `title: &str` parameter para que el
modal muestre "Editar carta natal" vs "Nueva carta natal". El
botón cambia "Crear carta" → "Guardar cambios" según el title.
## C — Single source of truth para OUTER_RING_MODULES
- engine exporta `pub const OUTER_RING_MODULES: &[&str] = &["transit",
"synastry", "planetary_return"]`
- shell elimina su const local, importa del engine
- canvas elimina 4 listas hardcodeadas (paint_wheel outer ring active
check + glyphs overlay + aspect_endpoints match) y usa contains() o
early-return sobre el slice. Próximo módulo outer-ring solo necesita
agregarse al const, no buscar copias.
## D — Labels ASC/MC/DESC/IC en el perímetro
Cuatro centered_glyphs en radii.sign_outer * 1.06 (justo afuera del
dial zodiacal, dentro del WHEEL_MARGIN) con color angle_highlight y
font 10px. El ojo identifica los 4 ángulos inmediatamente sin tener
que mapear la línea radial gruesa al ángulo correspondiente.
Las posiciones rotan con la rueda (drag del jog-dial los lleva).
`cargo check` y `cargo test` verdes. La fase agregó 6 controles
visibles al panel del NatalModule (4 view + 2 aspect filter + 1
slider) sin tocar la arquitectura de fases 6-15.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -31,7 +31,9 @@ use gpui::{
|
||||
use tahuantinsuyu_canvas::{
|
||||
AstrologyCanvas, CanvasEvent, CanvasMode, ThumbnailItem, ThumbnailScope,
|
||||
};
|
||||
use tahuantinsuyu_engine::{LayerKind, PipelineRequest, compose};
|
||||
use tahuantinsuyu_engine::{
|
||||
LayerKind, NatalOptions, OUTER_RING_MODULES, PipelineRequest, compose_with_options,
|
||||
};
|
||||
use tahuantinsuyu_model::{Chart, ChartId, ModuleState, TreeSelection};
|
||||
use tahuantinsuyu_panel::{ChartOption, ControlPanel, PanelEvent};
|
||||
use tahuantinsuyu_store::Store;
|
||||
@@ -287,6 +289,28 @@ impl Shell {
|
||||
siblings.into_iter().find(|c| c.id != current.id)
|
||||
}
|
||||
|
||||
/// Deriva las `NatalOptions` activas a partir del `module_configs["natal"]`.
|
||||
/// Si la entry no existe, devuelve defaults (majors=true, minors=false,
|
||||
/// multiplier=1.0).
|
||||
fn build_natal_options(&self) -> NatalOptions {
|
||||
let cfg = self.module_configs.get("natal");
|
||||
let read_bool = |key: &str, default: bool| -> bool {
|
||||
cfg.and_then(|c| c.get(key))
|
||||
.and_then(|v| v.as_bool())
|
||||
.unwrap_or(default)
|
||||
};
|
||||
let read_f64 = |key: &str, default: f64| -> f64 {
|
||||
cfg.and_then(|c| c.get(key))
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(default)
|
||||
};
|
||||
NatalOptions {
|
||||
show_majors: read_bool("aspect_majors", true),
|
||||
show_minors: read_bool("aspect_minors", false),
|
||||
orb_multiplier: read_f64("orb_multiplier", 1.0),
|
||||
}
|
||||
}
|
||||
|
||||
/// Lee `module_state` desde SQLite para la carta dada y los mergea
|
||||
/// con los defaults ya cargados en `module_configs`. Los valores
|
||||
/// persistidos ganan sobre los defaults.
|
||||
@@ -406,7 +430,13 @@ impl Shell {
|
||||
return;
|
||||
};
|
||||
let requests = self.build_requests();
|
||||
let render = match compose(chart, self.current_offset_minutes, &requests) {
|
||||
let natal_options = self.build_natal_options();
|
||||
let render = match compose_with_options(
|
||||
chart,
|
||||
self.current_offset_minutes,
|
||||
&requests,
|
||||
&natal_options,
|
||||
) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
eprintln!(
|
||||
@@ -475,8 +505,9 @@ impl Shell {
|
||||
} => {
|
||||
let bool_val = value.as_bool().unwrap_or(true);
|
||||
if module_id == "natal" {
|
||||
// Toggles puramente visuales — solo afectan visibility
|
||||
// del render actual, sin recomponer.
|
||||
// Distinguimos: show_* = visibility (no recompose),
|
||||
// aspect_*/orb_* = filtros de engine (recompose +
|
||||
// persist).
|
||||
let kind = match key.as_str() {
|
||||
"show_sign_dial" => Some(LayerKind::SignDial),
|
||||
"show_houses" => Some(LayerKind::Houses),
|
||||
@@ -487,6 +518,17 @@ impl Shell {
|
||||
if let Some(k) = kind {
|
||||
self.canvas
|
||||
.update(cx, |c, cx| c.set_layer_visible(k, bool_val, cx));
|
||||
} else {
|
||||
// Filtros: actualizar module_configs + recompose.
|
||||
let entry = self
|
||||
.module_configs
|
||||
.entry("natal".into())
|
||||
.or_insert_with(|| serde_json::json!({}));
|
||||
if let serde_json::Value::Object(map) = entry {
|
||||
map.insert(key.clone(), value.clone());
|
||||
}
|
||||
self.persist_module("natal");
|
||||
self.render_current(cx);
|
||||
}
|
||||
} else {
|
||||
// Cualquier otro módulo: actualizamos su config y
|
||||
@@ -540,9 +582,8 @@ impl Shell {
|
||||
// Helpers de module_configs
|
||||
// =====================================================================
|
||||
|
||||
/// Módulos que pintan en el outer ring del canvas — mutuamente
|
||||
/// excluyentes a nivel de UI. Al prender uno, los otros se apagan.
|
||||
const OUTER_RING_MODULES: &[&str] = &["transit", "synastry", "planetary_return"];
|
||||
// OUTER_RING_MODULES viene de tahuantinsuyu_engine — single source of
|
||||
// truth. Shell y canvas leen del mismo slice.
|
||||
|
||||
|
||||
/// Etiqueta breve para mostrar al elegir una carta en el picker:
|
||||
|
||||
Reference in New Issue
Block a user