feat(tahuantinsuyu): fase 14 — Return abstracto + Control::Select interactivo
El módulo SolarReturn se generaliza a PlanetaryReturn parametrizable
por cuerpo (Sun/Moon/Mercury/Venus/Mars/Jupiter/Saturn/Uranus/Neptune/
Pluto). Validado contra `Control::Select`, ahora interactivo como
tercer tipo de control draggable (después de Toggle/Slider/ChartPicker).
Refactor estructural: el dropdown del ChartPicker pasa a ser
infraestructura compartida — chart_picker_value/chart_picker_open
desaparecen, reemplazados por string_state/dropdown_open que sirven
a CUALQUIER control basado en string (picker + select).
render_chart_picker y render_select ahora son thin wrappers sobre
render_dropdown(options, include_auto).
- engine:
- PipelineRequest::SolarReturn → PipelineRequest::PlanetaryReturn
{ body: String, target_age_years }. Body como string agnóstico
(sun/moon/jupiter/...) que el bridge mapea a eternal_sky::Body
vía map_body — el mismo helper que ya usa StoredChartConfig.
- build_solar_return_overlay → build_planetary_return_overlay con
parameter `body: Body`. next_return acepta cualquier body, así que
Moon return (mensual) y Saturn return (29 años) funcionan igual.
Mensajes de error incluyen body.name() para diagnóstico.
- modules:
- SolarReturnModule → PlanetaryReturnModule (mod planetary_return).
id "planetary_return". Controles: toggle "enabled" + Select "body"
con 10 opciones de cuerpo (Sol → Plutón) + Slider edad. label
"Retornos planetarios".
- panel:
- Refactor: chart_picker_value/chart_picker_open → string_state/
dropdown_open (compartido entre ChartPicker y Select).
- set_string(module_id, key, value, cx) — API unificada. set_chart_picker
queda como alias retrocompatible.
- render_dropdown(options, include_auto, …) — helper común. picker
pasa include_auto=true (muestra "(automático)" + separador);
select pasa include_auto=false (las options son la única opción).
- render_select implementado — el botón muestra la option's label
(no value); click abre dropdown; click en opción emite ControlChanged
con Value::String(option.value).
- shell:
- OUTER_RING_MODULES const: "solar_return" → "planetary_return".
- build_requests para planetary_return: lee body string del
module_configs (default "sun"), arma PipelineRequest::PlanetaryReturn.
- apply_selection inicializa target_age + body=sun default para
planetary_return.
- sync_panel_from_configs strings → set_string (era set_chart_picker).
Probarlo: en el panel del módulo "Retornos planetarios", click en el
dropdown "Cuerpo" abre el popup; click en "Saturno" + slider en 29
años + toggle "Activar" = ves la carta del primer retorno de Saturno
(cuando recién terminaba la primera vuelta) en el outer ring con
cross aspects al natal.
NOTE: La persistencia con id "solar_return" de fase 13 queda huérfana
en la DB de los users que ya hayan probado. No es destructivo —
simplemente esas rows quedan sin módulo que las lea. Pre-1.0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -144,7 +144,7 @@ impl Shell {
|
||||
// edad actual. Estos quedan en module_configs como
|
||||
// valor base si el usuario nunca tocó el slider.
|
||||
self.module_configs.clear();
|
||||
for module_id in ["progression", "solar_arc", "solar_return"] {
|
||||
for module_id in ["progression", "solar_arc", "planetary_return"] {
|
||||
let entry = self
|
||||
.module_configs
|
||||
.entry(module_id.into())
|
||||
@@ -153,6 +153,16 @@ impl Shell {
|
||||
map.insert("target_age_years".into(), serde_json::json!(age));
|
||||
}
|
||||
}
|
||||
// El módulo planetary_return además necesita un body
|
||||
// por default — el shell elige "sun" si el usuario no
|
||||
// tocó el Select. La persistencia luego puede pisar
|
||||
// este valor.
|
||||
if let Some(serde_json::Value::Object(map)) =
|
||||
self.module_configs.get_mut("planetary_return")
|
||||
{
|
||||
map.entry(String::from("body"))
|
||||
.or_insert(serde_json::json!("sun"));
|
||||
}
|
||||
// 2) Sobreescribir con lo que el usuario persistió la
|
||||
// última vez para esta carta (SQLite `module_state`).
|
||||
self.load_persisted_module_states(chart.id);
|
||||
@@ -239,9 +249,17 @@ impl Shell {
|
||||
});
|
||||
}
|
||||
}
|
||||
if module_enabled(&self.module_configs, "solar_return") {
|
||||
let age = self.module_age_or_current("solar_return");
|
||||
requests.push(PipelineRequest::SolarReturn {
|
||||
if module_enabled(&self.module_configs, "planetary_return") {
|
||||
let age = self.module_age_or_current("planetary_return");
|
||||
let body = self
|
||||
.module_configs
|
||||
.get("planetary_return")
|
||||
.and_then(|c| c.get("body"))
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("sun")
|
||||
.to_string();
|
||||
requests.push(PipelineRequest::PlanetaryReturn {
|
||||
body,
|
||||
target_age_years: age,
|
||||
});
|
||||
}
|
||||
@@ -329,9 +347,9 @@ impl Shell {
|
||||
} else if let Some(f) = value.as_f64() {
|
||||
p.set_slider(module_id, key, f, cx);
|
||||
} else if let Some(s) = value.as_str() {
|
||||
p.set_chart_picker(module_id, key, Some(s.to_string()), cx);
|
||||
p.set_string(module_id, key, Some(s.to_string()), cx);
|
||||
} else if value.is_null() {
|
||||
p.set_chart_picker(module_id, key, None, cx);
|
||||
p.set_string(module_id, key, None, cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -524,7 +542,7 @@ impl Shell {
|
||||
|
||||
/// 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", "solar_return"];
|
||||
const OUTER_RING_MODULES: &[&str] = &["transit", "synastry", "planetary_return"];
|
||||
|
||||
|
||||
/// Etiqueta breve para mostrar al elegir una carta en el picker:
|
||||
|
||||
Reference in New Issue
Block a user