Cualquier carta del DB se puede elegir como partner de sinastría
(no solo hermanas del contacto actual). El módulo SynastryModule
declara un Control::ChartPicker; el panel renderea un dropdown que
abre un popup con todas las cartas; el shell inyecta las opciones y
resuelve el partner desde la selección persistida.
- modules: nueva variante Control::ChartPicker { key, label } sin
default — las opciones las inyecta el host. SynastryModule.controls()
agrega un picker con key="partner_chart_id".
- store: list_all_charts() — query sin filtros, ordenada por label
case-insensitive. Pensada para selectores cross-contact.
- panel:
- ChartOption struct público (id + label).
- chart_options Vec + chart_picker_value HashMap + chart_picker_open
Option (solo uno abierto a la vez).
- APIs públicas set_chart_options / set_chart_picker para sync.
- set_active_kind inicializa picker_value a None ("automático").
- render_chart_picker: botón "▾ label" en bg_button; al click toggle
un popup absolute con (automático) + separador + cada chart_option
clickable. select_picker emite ControlChanged con Value::String(id)
o Value::Null.
- shell:
- new() llama refresh_chart_options al final para popular el panel
desde el boot.
- refresh_chart_options() builds Vec<ChartOption> desde
list_all_charts (label + birth_brief "YYYY-MM-DD · Lugar"). Lo
llamamos también desde TreeEvent::HierarchyChanged para que el
dropdown refleje altas/bajas.
- resolve_synastry_partner reemplaza find_synastry_partner: 1) lee
module_configs["synastry"]["partner_chart_id"] y resuelve el chart;
2) fallback al automático (primera hermana). El fallback significa
que el módulo sigue funcionando sin elegir manualmente.
- sync_panel_from_configs ahora maneja Value::String / Value::Null
→ set_chart_picker, así el picker se restaura al cargar una carta.
Persistencia: el partner_chart_id va al config_json (fase 11), así
que cada carta recuerda con quién hizo sinastría la última vez.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Los `Control::Slider` del panel ya no son display-only — son arrastrables
con el mismo patrón del splitter (canvas absoluto sobre el track + window
mouse handlers en cada frame). El `ProgressionModule` ahora expone un
slider de `target_age_years` (0..120) que el shell inicializa con la
edad actual del sujeto al cargar la carta.
- panel: SliderDrag struct + slider_state HashMap + slider_drag Option
+ métodos start/continue/end_slider_drag + apply_slider_position que
calcula fraction desde la posición del mouse relativa al track y
emite ControlChanged con el valor float. set_slider(module, key, val)
para sincronización externa. set_active_kind ahora inicializa también
los sliders desde sus defaults. render_slider pinta track + portion
filled + thumb circular + canvas overlay con handlers de drag.
Los Slider tienen un valor visible "X.X (min...max)" en el header.
- modules: ProgressionModule agrega Control::Slider target_age_years
con range 0..120, step 0.25, default 30 (placeholder — el shell lo
reescribe con la edad real al cargar la carta).
- shell: apply_selection(Chart) ahora calcula current_age, lo inserta
en module_configs["progression"]["target_age_years"], y empuja al
panel via set_slider. build_requests ya leía target_age_years desde
el map (de fase 7), así que ahora el slider lo controla.
Mecánica: si activás "Progresión secundaria", el slider arranca en la
edad actual del sujeto. Arrastralo a la izquierda y la rueda recompone
la carta progresada para esa edad simbólica — vas viendo cómo el sujeto
"evoluciona" o "involuciona" a través de su línea temporal interna,
con los planetas progresados moviéndose por el anillo interno y los
cross aspects con la natal reorganizándose en tiempo real.
Same pattern aplica de aquí en más para cualquier slider futuro
(harmonic en NatalModule, target_year en SolarArc, orb_multiplier, …).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Time scrubbing por drag en el aro exterior del wheel: rota visualmente
mientras dura el drag, al soltar traduce el delta angular a minutos
(1° = 4 min sideral, CW = forward) y emite CanvasEvent::TimeOffsetChanged.
La Shell recomputa con engine::compute_at_offset y el ascendant rotado
queda en la nueva posición. Snap visual a 0° tras commit.
- engine: nueva variante compute_at_offset(chart, minutes) que suma
segundos al UTC base via add_seconds + Instant::from_utc y corre la
pipeline normal. compute() es ahora wrapper con offset=0.
- canvas: estado nuevo layer_visibility + drag_jog. Mouse handlers
registrados desde el paint callback (mismo patrón que splitter/tiled).
Hotkeys D/H/X/P toggle SignDial/Houses/Aspects/Bodies, R resetea
offset. FocusHandle + click-to-focus para recibir teclas. Indicador
⏱ ±Xd HH:MM en el footer con color highlight cuando el offset != 0.
paint_wheel + glyph overlays respetan layer_visibility (skip capas
ocultas).
- modules: NatalModule.controls() ahora expone show_sign_dial /
show_houses / show_aspects / show_bodies con hotkeys [D/H/X/P], más
el slider de armónico.
- panel: ControlPanel mantiene toggle_state cache (module_id, key) →
bool, inicializa desde defaults al cambiar de ChartKind. Click
invierte el toggle visualmente y emite ControlChanged. Nuevo
set_toggle(module, key, value) para que la Shell mantenga sync
cuando el canvas se autotogglea por hotkey.
- shell: nuevo current_chart + current_offset_minutes. render_current()
delega a compute_at_offset. Suscripción a CanvasEvent traduce
TimeOffsetChanged → re-render, LayerVisibilityChanged → panel sync.
Suscripción a PanelEvent::ControlChanged traduce show_* keys a
set_layer_visible sobre el canvas.
Todos los tests verdes. La fase 5 sumará módulos extra (transit,
progression, synastry, uranian) + extracción de eternal de lo que falte.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Módulo nuevo `modules/tahuantinsuyu/` con 9 crates reusables + app
`apps/tahuantinsuyu` ejecutable que abre la ventana del explorador y
coordina los widgets:
- tahuantinsuyu-card: Card Brahman + spawn_sidecar (flows
chart-request/chart-result).
- tahuantinsuyu-model: tipos agnósticos (Group/Contact/Chart,
StoredBirthData, StoredChartConfig, ChartKind, TreeSelection).
- tahuantinsuyu-store: persistencia SQLite (rusqlite) con migración v1,
CRUD por entidad y descenso recursivo `charts_under_group`.
- tahuantinsuyu-engine: bridge agnóstico al canvas vía `RenderModel`
(Layer/Glyph/Geometry). Feature `eternal-bridge` (off por default)
reservada para enchufar eternal-astrology desde ~/eternal.
- tahuantinsuyu-modules: registry de módulos pluggables (Module trait
+ Control schema) con `NatalModule` placeholder.
- tahuantinsuyu-theme: AstroPalette (elementos / modos / planetas /
aspectos) con variantes dark + light sobre yahweh-theme.
- tahuantinsuyu-canvas: widget GPUI con CanvasState (Empty / Wheel /
Thumbnails). Render placeholder hasta cablear la rueda real.
- tahuantinsuyu-tree: explorador izquierdo sobre yahweh-widget-tree,
prefijos g:/c:/h: para Group/Contact/Chart.
- tahuantinsuyu-panel: control panel inferior que lee Controls de los
módulos del registry y los pinta.
- apps/tahuantinsuyu: binario `tahuantinsuyu` (launch_app-style) con
Shell coordinador (tree↔canvas↔panel), DB en $XDG_DATA_HOME.
Workspace Cargo.toml actualizado con los 10 miembros. `cargo check`
verde, tests unitarios verdes (model/store/engine/modules/theme/card).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>