refactor(tahuantinsuyu): fase 6 — Modules pluggables vía compose + PipelineRequest

El shell ya no carga el flag `show_transits: bool` ni hardcodea qué
pipeline corre. La engine expone una sola API `compose(chart, offset,
&[PipelineRequest])` que la shell alimenta a partir de un map
`module_configs: HashMap<String, serde_json::Value>`. Los toggles de
overlay (transit hoy, progression/synastry/solar_arc en fase 7) viven
como módulos propios en el panel.

- engine: PipelineRequest enum (variante Transit por ahora; comentarios
  con el roadmap de SecondaryProgression/SolarArc/Synastry). compose()
  es la nueva entrada canónica; compute / compute_at_offset /
  compute_with_transits_at_now quedan como atajos retrocompatibles que
  delegan en compose. bridge.rs refactor: extraído build_transit_overlay
  como helper que muta &mut RenderModel, listo para que más pipelines
  apilen capas encima.
- modules: nuevo módulo `transit::TransitModule` (id "transit", toggle
  "enabled" con hotkey [T], applies_to Natal). Sacado el toggle
  show_transits de NatalModule — ahora cada módulo declara lo suyo.
  Registry::with_builtins() registra ambos. Test asegura los dos
  aplican a Natal.
- panel: sin cambios — ya itera Registry::for_kind(kind) y renderea
  cada módulo aplicable con sus controls. La adición del TransitModule
  aparece automática como segunda card en el panel.
- shell: replace show_transits por module_configs map. build_requests()
  deriva PipelineRequest::Transit cuando module_configs["transit"]
  ["enabled"] == true. on_panel_event: toggles del NatalModule afectan
  solo visibility del canvas; toggles de otros módulos van al
  module_configs y disparan render_current. on_canvas_event: [T]
  hotkey → flip transit.enabled + sync panel + recompose. apps Cargo
  agrega serde_json como dep directa.

Todos los tests verdes. Fase 7 puede sumar overlays adicionales
(progression, solar_arc) solo agregando variantes a PipelineRequest +
helpers en bridge + módulos declarativos — sin tocar el shell.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
sergio
2026-05-17 10:31:11 +00:00
parent 4d14a4495f
commit d4761bf238
6 changed files with 215 additions and 117 deletions
@@ -123,6 +123,7 @@ impl Registry {
pub fn with_builtins() -> Self {
let mut r = Self { modules: Vec::new() };
r.register(Box::new(natal::NatalModule));
r.register(Box::new(transit::TransitModule));
r
}
@@ -203,12 +204,6 @@ pub mod natal {
default: true,
hotkey: Some("P".into()),
},
Control::Toggle {
key: "show_transits".into(),
label: "Tránsitos (ahora)".into(),
default: false,
hotkey: Some("T".into()),
},
Control::Slider {
key: "harmonic".into(),
label: "Armónico".into(),
@@ -229,15 +224,68 @@ pub mod natal {
}
}
// =====================================================================
// TransitModule — overlay del cielo del momento sobre la carta natal
// =====================================================================
pub mod transit {
use super::*;
/// Anillo externo con las posiciones planetarias del **instante
/// actual** (reloj de pared) sobre el sujeto natal, más las
/// cross-aspects natal × transit. La engine despacha al pipeline
/// `PipelineRequest::Transit` cuando este módulo está activo en el
/// `module_configs` del shell.
pub struct TransitModule;
impl Module for TransitModule {
fn id(&self) -> &'static str {
"transit"
}
fn label(&self) -> &'static str {
"Tránsitos"
}
fn description(&self) -> &'static str {
"Cielo del momento sobre la natal + cross aspects."
}
fn applies_to(&self, kind: ChartKind) -> bool {
// Por ahora solo overlay sobre cartas natales — más adelante
// podríamos overlayar tránsitos sobre Progresiones, etc.
matches!(kind, ChartKind::Natal)
}
fn enabled_by_default(&self) -> bool {
false
}
fn controls(&self) -> Vec<Control> {
vec![Control::Toggle {
key: "enabled".into(),
label: "Activar".into(),
default: false,
hotkey: Some("T".into()),
}]
}
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
// Las capas de tránsito se construyen en la engine vía
// `PipelineRequest::Transit` porque necesitan acceso a la
// NatalChart cruda + EphemerisSession. Este método queda
// como no-op — el módulo es puramente declarativo.
Vec::new()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_finds_natal() {
fn registry_finds_builtins() {
let r = Registry::with_builtins();
assert!(r.find("natal").is_some());
assert_eq!(r.for_kind(ChartKind::Natal).len(), 1);
assert!(r.find("transit").is_some());
// Natal kind tiene 2 módulos aplicables: el propio + transit overlay.
assert_eq!(r.for_kind(ChartKind::Natal).len(), 2);
// Synastry kind no tiene módulos hoy.
assert!(r.for_kind(ChartKind::Synastry).is_empty());
}
}