e044d475167b0b210a79df049541c22d61edd4e2
17 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a4d1e0dc17 |
feat(tahuantinsuyu): fase 27 — Lots helenísticos + 9 fixed stars
Dos módulos astrológicos pluggables más: - LotsModule: 7 Arabic Parts vía `all_lots(natal)` (Fortune, Spirit, Eros, Necessity, Courage, Victory, Nemesis). Glifos `lot:Fo` en ring 0.54, hover muestra el nombre completo. - FixedStarsModule: 9 estrellas notables (Aldebaran, Regulus, Antares, Fomalhaut, Spica, Sirius, Algol, Vega, Pollux) con longitudes tropicales J2000 + precesión general de 50.29″/año proyectada al año natal. Marcadores `✦Xxx` en ring 1.04. Registry pasa de 9 a 11 módulos; test actualizado. Sin cambios de esquema en RenderModel — los `LayerKind::Lots` y `LayerKind::FixedStars` ya existían. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d890bd4b3a |
feat(tahuantinsuyu): fase 21 — background compute + UranianModule
Cierre del brief original — última pieza visual (Uraniano) + perf. ## #1 — Compute en background thread render_current() pasa de bloqueante a async. La pipeline corre en cx.background_executor().spawn (no UI thread), y al terminar el update vuelve al UI vía cx.spawn. Sin esto, un drag del slider con muchos overlays bloquea el frame por hasta 200ms. Cancelación: Shell gana `render_seq: u64`. Cada render_current() incrementa el counter y captura su número; el closure async compara antes de aplicar. Si llegó un compute más nuevo en el medio (drag rápido), el viejo se descarta — evita el race donde un cómputo lento sobrescribe uno reciente y rápido. Inputs al background: Chart clonado + offset + Vec<PipelineRequest> + NatalOptions. La sesión VSOP2013 sigue siendo `static OnceLock` read-only, accesible desde cualquier thread. ## #11 — UranianModule (versión textual) Cierra la última pieza del brief original. Toggle "Uraniano (90°)" en el panel; engine detecta cuerpos natales cuya longitud módulo 90 cae dentro de ε=2° y los agrupa como "ejes". Footer renderea cada grupo como pill con los unicodes (☉ ♃ · 14.3°) bajo el header "Ejes uranianos (90°)". El algoritmo: 1. mod90 = longitude.rem_euclid(90.0) para cada placement 2. Sort por mod90 ascendente 3. Walk lineal agrupando entradas con diff(mod90) ≤ ε 4. Wrap-around check: el primer y último grupo se mergean si abarcan el cierre del dial (88→2 = solo 4° de diff modular) 5. Solo emite grupos con 2+ miembros (singletons no son fórmulas) - engine: PipelineRequest::Uranian + UranianGroup struct + build_uranian_groups helper. RenderModel gana uranian_groups field. push_overlay_meta tipo "Uraniano · N ejes" o "sin ejes". - modules: uranian::UranianModule (toggle "Activar"). Registry pasa a 9 módulos para ChartKind::Natal. Test actualizado. - shell: build_requests detecta uranian.enabled, pushea PipelineRequest::Uranian (sin parámetros). - canvas: footer agrega sección "Ejes uranianos (90°)" con pills arriba de la lista de aspectos — border angle_highlight para invitar a la lectura. La visualización geométrica completa del dial de 90° con árbol de simetría al hover queda para una fase posterior — esta versión textual cubre el caso analítico (ver qué cuerpos están "en relación uraniana") sin requerir un canvas secundario. cargo check verde, 8 tests engine + 1 test modules (9 módulos aplicables a ChartKind::Natal) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d3649bfd1a |
feat(tahuantinsuyu): fase 20 — accordion + lunar shift + CompositeModule + 90 ciudades
Cuatro features que cierran el set inicial de funcionalidades de
fase 1:
## D — Acordeón colapsable en el panel
Cuando hay 8 módulos en el panel se llenaba de cards. Ahora cada card
es expandible/colapsable por click en el header. Defaults:
- Natal siempre expanded
- Módulos con toggle "enabled" = true → expanded
- Resto → collapsed
El usuario puede forzar cualquiera vía override (collapse_overrides
HashMap). Chevron ▾/▸ a la izquierda del header. Hover sobre el
header lo resalta para invitar al click.
## B — Lunar return shift (navegación mensual)
PipelineRequest::PlanetaryReturn gana campo `shift_days: i64` (range
±180). El bridge lo suma a after_seconds del search anchor antes de
next_return. Para Solar return típicamente 0 (mantiene comportamiento).
Para Moon return, mover el slider ±28 días salta al retorno lunar
anterior o siguiente, permitiendo navegar mes a mes la lunación que
le toca al sujeto cumplido N años. PlanetaryReturnModule.controls()
agrega un slider "Shift días (lunar nav)". El badge del overlay
muestra "Moon return 38a +14d" cuando shift_days != 0. Helper
`planetary_return_request(body, age)` para callers que no necesitan
shift (zero default).
## C — CompositeModule
Carta compuesta (midpoint Davison) entre la natal del sujeto y otra
carta partner. Cada placement compuesto es el angular midpoint entre
los dos correspondientes. Engine: `PipelineRequest::Composite {
partner_chart: Box<Chart> }` + build_composite_overlay que llama
`eternal_astrology::composite()`. Renderiza placements en
`radii.composite = r * 0.32` (entre solar_arc 0.40 y aspects 0.24,
re-balanced). Módulo `composite::CompositeModule` con toggle +
ChartPicker (mismo patrón que synastry).
Shell: resolve_composite_partner reusa el fallback al primer hermano
del contacto, igual que synastry.
## A — 90 ciudades expandidas + dropdown scrollable
CITY_PRESETS pasa de 25 a 90 ciudades cubriendo:
- Latinoamérica (35): todas las capitales + grandes ciudades de AR/
VE/CO/PE/CL/EC/UY/PY/BO/MX/CU/PR/CR/PA/SV/GT/HN/NI/DO/BR
- España (5) + Europa (20): Madrid/Barcelona/Sevilla/Valencia/Bilbao
+ London/Paris/Berlin/München/Roma/Milano/Amsterdam/Bruxelles/Wien/
Zürich/Lisboa/Dublin/Stockholm/Oslo/København/Helsinki/Warszawa/
Praha/Budapest/Athina/İstanbul/Moskva
- USA + Canadá (12): NY/LA/Chicago/Miami/Houston/SF/Seattle/Boston/
DC + Toronto/Montreal/Vancouver
- Asia (16): Tokyo/Beijing/Shanghai/HK/Singapore/Seoul/Bangkok/
Jakarta/Manila/Mumbai/Delhi/Bangalore/Karachi/Tehran/Dubai/Tel Aviv
- África (6): Cairo/Lagos/Nairobi/Johannesburg/Cape Town/Casablanca
- Oceanía (3): Sydney/Melbourne/Auckland
El popup del dropdown ahora es scrollable (h=360px, overflow_y_scroll)
con id estable para no perder scroll position entre re-renders.
cargo check verde, 8 tests engine + 1 test modules (8 módulos
aplicables a ChartKind::Natal) verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
32ab22f954 |
feat(tahuantinsuyu): fase 19 — theme switcher + house tooltips + midpoints + city presets
Fase completa con 4 mejoras independientes que aprovechan toda la
infraestructura previa. La aplicación ahora cubre lecturas profundas
(midpoints uranianos), accesibilidad visual (tooltips de cusps),
personalización (6 themes vía yahweh-widget-theme-switcher) y
usabilidad pragmática (city presets en el form).
## C — Theme switcher en header
- apps/tahuantinsuyu: nueva dep yahweh-widget-theme-switcher.
- shell render(): theme_switcher(cx) en el extremo derecho del header
(con flex_grow del divider del medio). Click cicla entre los 6
presets de yahweh-theme (Nebula, Aurora, Sunset, FlatDark,
SolarizedLight, HighContrast). AstroPalette::for_theme(theme) lee
is_dark, así toda la rueda se re-tinta automáticamente.
## B — Tooltips sobre house cusps
- canvas: HoverInfo deja de ser struct para ser enum con variantes
Body { ... } y HouseCusp { house_number, deg, local_x, local_y }.
Helpers .local() y .key() unifican el acceso.
- on_hover_check: primero hit-test bodies (threshold 14px); si no hubo
match Y el mouse está dentro del anillo de casas
(houses_inner..houses_outer ± 6px), calcula la longitud zodiacal
desde el ángulo de pantalla (inversa de polar_to_screen) y busca el
cusp más cercano (proximidad angular < 2.5°). HoverInfo::HouseCusp.
- Tooltip render: "Cusp Casa N · Signo XX.X°".
## D — MidpointsModule (Uranian-lite)
- engine: PipelineRequest::Midpoints (sin parámetros, default empty).
- bridge: build_midpoints_overlay computa midpoints entre todos los
pares de placements donde involucran Sol o Luna (~10 puntos según
body set). Fórmula: si |a-b|>180, mid=((a+b)/2+180) mod 360, sino
(a+b)/2 mod 360. Emite como Layer { kind: Midpoints, module_id:
"midpoints", ring: 0.62 } con Glyph.symbol="sun/jupiter" y
annotation="Sun/Jupiter".
- modules: midpoints::MidpointsModule con toggle "Activar". Registry
pasa a 7 módulos. Test actualizado.
- shell: build_requests detecta midpoints.enabled, pushea
PipelineRequest::Midpoints (no toma age ni body — es derivado puro).
- canvas: Radii agrega midpoints: r * 0.62 (entre houses_inner y
bodies natales). body_ring("midpoints") y aspect_endpoints retornan
ese radio. paint_wheel agrega un loop para LayerKind::Midpoints
pintando dots pequeños (r=0.012, alpha 0.7 sobre house_cusp color)
— los midpoints no llevan unicode symbol propio (no existe en
Unicode astrológico estándar). El detalle del par viene en hover.
- Hover sobre un midpoint: tooltip muestra "☉/♄ Tauro 14.3° ·
Sun/Jupiter" (display_symbol parsea "a/b" en dos unicodes;
annotation incluye nombres completos eternal).
## A — City presets en el ChartForm
- tree: nueva const CITY_PRESETS con 25 ciudades (Latinoamérica
capitales + 5 europeas + 5 anglosajonas + Tokyo/Sydney/Mumbai/Cairo)
con (name, lat, lon, tz_offset_minutes) sin DST. CityPreset struct.
- tree: TahuantinsuyuTree gana city_picker_open: bool. close_modal
lo resetea. toggle_city_picker + apply_city_preset(preset) helpers.
apply_city_preset lee el Modal activo (CreateChart o EditChart),
llama TextInput::set_text en place/lat/lon/tz del ChartForm,
cierra el picker.
- render_chart_form: title_row ahora tiene "📍 Ciudad rápida ▾"
button a la derecha del title. Click → toggle. Cuando picker_open,
popup absoluto debajo con la lista de presets. Click en preset →
autocompleta + cierra. El usuario sigue pudiendo editar manualmente
cualquier campo después; el preset es solo un punto de partida
rápido para evitar tipear coordenadas a mano.
cargo check verde, 8 tests engine + 1 test modules (7 módulos)
verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
2cd34c82da |
feat(tahuantinsuyu): fase 18 — aspect list + hover tooltips + dignidades + export SVG
Fase grande con 4 features que aprovechan toda la infraestructura ya
construida. Engine ganó 2 módulos nuevos (dignity table data-only +
svg_export), el RenderModel se enriqueció con AspectSummary y los
glyphs con dignity_marker, y el canvas trae hit-test pasivo + lista
textual + botón de export.
## C — Lista textual de aspectos
- engine: nuevo `AspectSummary { module_id, from_body, to_body, kind,
orb_deg, applying }` + campo `aspect_summary: Vec<AspectSummary>`
en RenderModel. populate_natal_aspect_summary y
populate_cross_aspect_summary se llaman desde compose por cada
pasada (natal + 4 overlays). Ordenado por orb_deg asc (los más
exactos primero).
- canvas: nuevo aspect_unicode helper (☌ ☍ △ □ ⚹ ⚻ ⚺ ∠ ⚼ Q bQ).
Footer agrega un grid flex-wrap con las top 12 entries del summary,
cada una formateada como "[module_id] ☉ △ ☾ · 2.3° A" coloreado
por palette.aspect(kind).
## A — Tooltips al hover
- canvas: nuevo HoverInfo { module_id, symbol, deg, house, retrograde,
dignity_marker, annotation, local_x, local_y } + state.hover.
on_hover_check ejecuta hit-test sobre todos los glyphs Bodies +
Outer (threshold 14px); se llama desde el handler MouseMoveEvent
cuando NO está dragging (handler reescrito para soportar drag y
hover en mismo callback). Cuando mouse sale del wheel, hover=None.
- Tooltip absoluto: "☉ Tauro · 23.4° · Casa 5 · ℞" con border
angle_highlight. Posición offset arriba-derecha del planeta,
clampada al wheel para no salirse.
## B — Dignidades esenciales clásicas
- engine: nuevo mod `dignity` con `Dignity { Rulership/Exaltation/
Detriment/Fall }` + tabla rules_classical (7 planetas tradicionales)
+ exalts_at table. `essential_dignity(body, sign_index) -> Option`.
4 tests cubren rulership/detriment/exaltation/fall + edge case
modernos (Urano/Nept/Plutón sin dignidad clásica). 4 markers:
+ (domicilio), · (exaltación), − (exilio), * (caída).
- engine: Glyph gana campo `dignity_marker: Option<String>`. Default
derive en Glyph para no romper N construction sites. bridge::
annotate_dignities mutua RenderModel post-build agregando markers
a glyphs natales según el signo de cada placement.
- NatalOptions agrega show_dignities. NatalModule.controls() agrega
Toggle "Dignidades esenciales (+ · − *)" default false.
- canvas: glyph render append dignity_marker al texto después del ᴿ.
## D — Export SVG
- engine: nuevo `pub mod svg_export` con `render_to_svg(&RenderModel)
-> String`. Reproduce la geometría del canvas en un SVG standalone
800×800 escalable: anillos zodiacales, cusps, planetas con
retrograde+dignity markers, aspectos coloreados por kind (cross
conocen rings de origen/destino vía aspect_radii), labels ASC/MC/
DESC/IC. Sin dependencias nuevas — write! sobre String. Test
asserts well-formed XML.
- canvas: CanvasEvent::ExportSvgRequested + botón pequeño "⬇ SVG"
en el header del wheel (al lado del title).
- shell: on_canvas_event ExportSvgRequested → export_current_to_svg
recompose actual + svg_export::render_to_svg + write a
$XDG_DATA_HOME/tahuantinsuyu/exports/<label>_<short_id>.svg.
Ruta logueada a stderr para que el usuario encuentre el archivo.
`cargo check` y `cargo test` verdes con 8 tests en engine
(2 existentes + 4 dignity + 1 svg + 1 mock).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
0ae622550d |
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>
|
||
|
|
6d572c81ca |
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>
|
||
|
|
8d95833c20 |
feat(tahuantinsuyu): fase 13 — Solar Return como sexto overlay
Sexto módulo siguiendo el patrón establecido. Cambio estructural:
3-way mutual exclusion para los módulos que comparten el outer ring
(transit + synastry + solar_return). Constante OUTER_RING_MODULES
abstrae el grupo para que fase 14+ pueda sumar lunar return / planet
returns sin tocar la lógica del shell.
- engine: PipelineRequest::SolarReturn { target_age_years } +
build_solar_return_overlay. Llama eternal_astrology::next_return
(Sun back to natal Sun, ventana ±1.5 años) desde un instante
~30 días antes del cumpleaños target. Computa la carta natal
completa al return_instant (mismo observer + config natales —
convención clásica) y la apila como Outer + Aspects cross natal ×
return. z=12/13. Import: `next_return` añadido a la lista de
re-exports del bridge.
- modules: solar_return::SolarReturnModule (id "solar_return", toggle
+ slider target_age_years 0..120 step 1.0). Registry pasa a 6
módulos para Natal.
- shell: OUTER_RING_MODULES const con los tres ids; mutual exclusion
generalizada de pair-wise a N-way (for-loop sobre el slice). Init
de age en apply_selection ahora incluye solar_return. build_requests
agrega la rama. Misma estructura que progression/solar_arc en la
rama de age handling.
- canvas: aspect_endpoints("solar_return") = (bodies, transits). Tres
loops del outer ring (paint dots, paint glyphs, anillos guía) ahora
aceptan los tres module_ids.
Probarlo: en el panel, slider "Edad del retorno" en valor entero (ej.
36) + toggle "Activar" = ves la carta del año cuando volviste a tu
Sol natal a los 36, con todos sus planetas en el outer ring y cross
aspects con tu natal. Cambiando el slider podés explorar año por año.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d9e21fbedc |
feat(tahuantinsuyu): fase 12 — Control::ChartPicker para Synastry
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>
|
||
|
|
97a6aab883 |
feat(tahuantinsuyu): fase 10 — Sinastría como overlay (bi-wheel con carta hermana)
Quinto módulo overlay. Cuando hay otra carta hermana del mismo
contacto, la sinastría pone las posiciones del partner en el outer
ring + dibuja cross aspects entre las dos personas. Mismo molde que
los overlays anteriores; única novedad: el PipelineRequest transporta
una `Chart` completa porque el partner no es derivable de la natal.
- engine: PipelineRequest::Synastry { partner_chart: Box<Chart> }.
build_synastry_overlay(natal, partner_chart, render) llama
compute_natal_chart sobre el partner y find_synastry_aspects entre
los dos NatalCharts (sólo majors). Layers con module_id="synastry"
y z=10/11. Reusa la helper compute_natal_chart de fase 5.
- modules: synastry::SynastryModule (id "synastry", toggle "Activar"
sin hotkey por ahora). Registry agrega el quinto built-in. Test
pasó a 5 módulos aplicables a ChartKind::Natal.
- shell: build_requests detecta synastry.enabled y llama
find_synastry_partner — busca la primera carta hermana del contacto
actual (mismo contact_id, distinto chart_id). Si no hay hermana,
skip silencioso. Mutual exclusion: al prender transit o synastry
se apaga el otro automáticamente (comparten outer ring) — sincroniza
el toggle del panel + el layer_visibility del canvas.
- canvas: Radii::aspect_endpoints("synastry") devuelve (bodies,
transits) — same slot que transit. Loops del outer ring aceptan
module_id "transit" OR "synastry" (paint_wheel + glyph overlay).
Sin radii nuevo — visualmente comparten el ring 0.82 con transit.
Para probarlo: creá dos cartas en el mismo contacto (ej. el sujeto +
su pareja). Abrí la primera y activá "Sinastría" en el panel. Verás
los planetas del partner en el outer ring + líneas que cruzan al
centro mostrando los aspectos entre las dos personas. Si tenés
transit prendido cuando lo activás, se apaga; al revés también.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
1a3bc55016 |
feat(tahuantinsuyu): fase 9 — Solar Arc como segundo overlay
Confirma que la arquitectura de fase 6 escala: tres overlays simultáneos
(transit + progression + solar_arc) sin acoplamiento entre módulos, y
sin tocar el flujo del Shell salvo registrar el nuevo branch.
Tres puntos de extensión por overlay nuevo (exactamente los predichos):
1. variante en PipelineRequest
2. helper build_*_overlay en bridge + match arm en compose
3. módulo declarativo en modules/ + registro
- engine: PipelineRequest::SolarArc { target_age_years: f64 } +
build_solar_arc_overlay que llama solar_arc_true(natal, session, age)
→ desplaza uniformemente cada placement y cusp por el arco solar
(default ≈1°/año, vía true progressed Sun). Cross aspects natal ×
dirigida vía find_synastry_aspects(majors). Layers con
module_id="solar_arc" y z=8/9 (sobre todos los demás).
- modules: solar_arc::SolarArcModule con id="solar_arc", toggle
"Activar" + slider target_age_years 0..120. Mismo shape que
ProgressionModule. Registry.with_builtins lo registra. Test pasó a
4 módulos aplicables a ChartKind::Natal.
- canvas: Radii.solar_arc = 0.40 (entre progression 0.48 y aspects),
aspects shrunk a 0.32 para hacer lugar. Helpers Radii::body_ring()
y Radii::aspect_endpoints() ahora reconocen "solar_arc". paint_wheel
itera ambos overlays (progression + solar_arc) para dibujar dots,
glyph overlays y anillos guía sutiles. Loop común `for (id, ring) in
[..]` evita duplicación de código.
- shell: build_requests detecta solar_arc.enabled, agrega request con
edad. apply_selection inicializa target_age_years para ambos
overlays (progression + solar_arc) en current_age + sincroniza los
sliders del panel. Helper module_age_or_current(id) factoriza la
lectura de edad con fallback.
Activando los tres overlays al mismo tiempo el canvas se convierte en
una rueda de cinco anillos: zodíaco (1.00), tránsito (0.82), natal
(0.66-0.78), bodies natal (0.58), progression (0.48), solar arc (0.40),
con líneas de aspectos cross convergiendo desde el ring natal hacia
cada overlay simultáneamente.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e0c5c02b8e |
feat(tahuantinsuyu): fase 8 — slider interactivo + slider de edad en progression
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> |
||
|
|
42e09fd7cd |
feat(tahuantinsuyu): fase 7 — progresión secundaria como overlay (prueba de la arquitectura)
Valida que el refactor de fase 6 escala: agregar un overlay nuevo
(progresión secundaria, "día por año") tocó exactamente lo predicho —
una variante en PipelineRequest, un helper en bridge, un módulo
declarativo en `progression`, una línea en build_requests, y el canvas
para pintarlo. Cero cambios en el flujo de eventos del Shell.
- engine: PipelineRequest::SecondaryProgression { target_age_years: f64 }
+ build_progression_overlay(natal, age, render) que delega en
eternal_astrology::secondary_progression(natal, session, age), pinta
los placements progresados en un anillo interno (ring 0.48), y suma
cross-aspects natal × progresada vía find_synastry_aspects (sólo
mayores, opacidad × 0.7). z = 6/7 — sobre las capas natal y
transit.
- modules: progression::ProgressionModule con id "progression", toggle
"Activar" (sin hotkey por ahora). Registry::with_builtins lo agrega.
El test pasó de 2 a 3 módulos para ChartKind::Natal.
- shell: build_requests detecta progression.enabled, calcula la edad
decimal desde StoredBirthData y SystemTime::now() (current_age_years
helper, aproximación tropical) y arma el request con esa edad.
El resto del flujo del shell se mantiene — la abstracción funciona.
- canvas: Radii agrega `progression: r * 0.48`, `aspects` shrunk a
`r * 0.38` para hacer lugar. Helper aspect_endpoints(module_id)
resuelve el par (r_from, r_to) según natal/transit/progression.
paint_wheel pinta dots progresados con alpha 0.85 + anillo guía
sutil que delimita el slot. Glyph overlay pinta planet symbols en
el ring de progresión con font_size 14 y box 20 (menores que el
natal para diferenciar visualmente).
Probarlo: en el panel, activar "Progresión secundaria" — verás los
planetas progresados en un anillo interno con su retrogradación
marcada, y líneas de aspectos que cruzan desde el ring de cuerpos
natales hacia el ring progresivo. Combinable con tránsitos: ambos
overlays apilan capas en orden bodies → transits, sin colisiones.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d4761bf238 |
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> |
||
|
|
4d14a4495f |
feat(tahuantinsuyu): fase 5 — overlay de tránsitos (bi-wheel natal × ahora)
Activá el toggle "Tránsitos (ahora)" en el panel (o hotkey [T] sobre el wheel): la engine computa una segunda NatalChart al instante SystemTime::now() con el mismo observer y dibuja un anillo externo de planet glyphs encima del natal, más las cross-aspects entre ambos charts (sólo mayores). Las líneas cross van del ring de cuerpos natales al ring externo de tránsitos, con stroke más fino y opacidad más baja para no taparle el ojo a las aspectos natal-natal. - engine/bridge.rs: extraídas build_eternal_inputs y compute_natal_chart como helpers reutilizables. Nueva compute_with_transits(chart, offset, transit_at) que llama find_synastry_aspects entre natal y transit (AspectKind::MAJORS). Atajo compute_with_transits_at_now usa ESInstant::now(). Las capas extra van con module_id = "transit" y LayerKind::Outer / LayerKind::Aspects para que el canvas las distinga. - engine/lib.rs: re-export de compute_with_transits_at_now con el mismo fallback al mock cuando feature `eternal-bridge` está off. - canvas: nueva Radii::transits = 0.82, layout del wheel re-balanceado (houses_outer 0.78, houses_inner 0.66, bodies 0.58, aspects 0.50) para hacer lugar al anillo externo sin colisiones. paint_wheel: detecta layers de transit por module_id, pinta dots + glifos en el anillo nuevo + anillos guía sutiles. paint_cross_aspect_line con stroke 0.7 entre los dos radios. Glyph overlay para Outer ring con alpha 0.9 y font_size más chico que el natal. Hotkey [T] en on_key_down toggle LayerKind::Outer. - modules: NatalModule.controls() agrega toggle show_transits con hotkey [T] (default false — no recomputar transits si nadie pidió). - shell: nuevo show_transits flag. render_current despacha entre compute_at_offset y compute_with_transits_at_now según el flag. on_panel_event traduce ControlChanged show_transits a flip + redraw. on_canvas_event: el toggle de LayerKind::Outer dispara show_transits flip + render (no es un visibility toggle puro). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
360797132e |
feat(tahuantinsuyu): fase 4 — jog-dial perimetral, hotkeys y panel interactivo
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> |
||
|
|
c48638fe87 |
feat(tahuantinsuyu): scaffolding del estudio astrológico (10 crates + ventana 3-panes)
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> |