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:
@@ -244,10 +244,20 @@ pub fn compose(
|
||||
chart: &Chart,
|
||||
offset_minutes: i64,
|
||||
requests: &[crate::PipelineRequest],
|
||||
natal_options: &crate::NatalOptions,
|
||||
) -> Result<RenderModel, EngineError> {
|
||||
let t0 = Instant::now();
|
||||
let (natal, config_e, observer) = compute_natal_chart(chart, offset_minutes)?;
|
||||
let aspects = find_aspects(&natal, &OrbTable::modern_western());
|
||||
let orb_table = build_orb_table(natal_options.orb_multiplier);
|
||||
let all_aspects = find_aspects(&natal, &orb_table);
|
||||
let aspects: Vec<Aspect> = all_aspects
|
||||
.into_iter()
|
||||
.filter(|a| {
|
||||
let is_major = EAspectKind::MAJORS.contains(&a.kind);
|
||||
(is_major && natal_options.show_majors)
|
||||
|| (!is_major && natal_options.show_minors)
|
||||
})
|
||||
.collect();
|
||||
let mut render = build_render_model(chart, &natal, &aspects, t0);
|
||||
|
||||
for req in requests {
|
||||
@@ -776,13 +786,10 @@ fn build_render_model(
|
||||
};
|
||||
|
||||
// ─── Capa 3: Aspects ──────────────────────────────────────────────
|
||||
// Los aspects ya vienen filtrados por NatalOptions (majors / minors)
|
||||
// desde compose(). Acá solo mapeamos a LineSeg.
|
||||
let mut aspect_lines: Vec<LineSeg> = Vec::with_capacity(aspects.len());
|
||||
for a in aspects {
|
||||
// Solo los aspectos mayores se pintan en este pase — los menores
|
||||
// saturan visualmente. Fase 4 pondrá un toggle para mostrarlos.
|
||||
if !EAspectKind::MAJORS.contains(&a.kind) {
|
||||
continue;
|
||||
}
|
||||
let pa = natal.placement(a.a);
|
||||
let pb = natal.placement(a.b);
|
||||
if let (Some(pa), Some(pb)) = (pa, pb) {
|
||||
@@ -834,6 +841,27 @@ fn build_render_model(
|
||||
}
|
||||
}
|
||||
|
||||
/// Construye una `OrbTable` con los orbes default de `modern_western`
|
||||
/// escalados por `multiplier`. Necesario porque eternal expone
|
||||
/// `set_orb` pero no permite iterar los base orbs internos.
|
||||
fn build_orb_table(multiplier: f64) -> OrbTable {
|
||||
let mut t = OrbTable::modern_western();
|
||||
let m = multiplier.max(0.0);
|
||||
t.set_orb(EAspectKind::Conjunction, 8.0 * m);
|
||||
t.set_orb(EAspectKind::Opposition, 8.0 * m);
|
||||
t.set_orb(EAspectKind::Trine, 7.0 * m);
|
||||
t.set_orb(EAspectKind::Square, 7.0 * m);
|
||||
t.set_orb(EAspectKind::Sextile, 5.0 * m);
|
||||
t.set_orb(EAspectKind::Quincunx, 2.5 * m);
|
||||
t.set_orb(EAspectKind::SemiSextile, 2.0 * m);
|
||||
t.set_orb(EAspectKind::SemiSquare, 2.0 * m);
|
||||
t.set_orb(EAspectKind::Sesquiquadrate, 2.0 * m);
|
||||
t.set_orb(EAspectKind::Quintile, 1.5 * m);
|
||||
t.set_orb(EAspectKind::BiQuintile, 1.5 * m);
|
||||
t.set_orb(EAspectKind::Septile, 1.5 * m);
|
||||
t
|
||||
}
|
||||
|
||||
fn push_overlay_meta(render: &mut RenderModel, module_id: &str, label: String) {
|
||||
render.overlays.push(OverlayMeta {
|
||||
module_id: module_id.to_string(),
|
||||
|
||||
Reference in New Issue
Block a user