2728698f5ef15234be868988d5c330ef6c0f2533
348 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e570c6ca6f |
docs: fix factual errors en SDDs y STATUS/ROADMAP
Errores detectados al auditar afirmaciones técnicas contra el código: 1. minga-vfs: NO está relacionado con Mónadas (esas son de akasha). Es FUSE que proyecta el índice de minga (git semántico) como filesystem, resolviendo paths virtuales a blobs por hash. 2. protocol/SDD.md: Card tiene 19 campos, no 6. Añadido bloque con anatomía completa del struct. 3. STATUS.md: LOC por capa corregidos contra wc -l real - protocol: 6,260 → 7,278 - init: ~3,600 → 4,301 - compat: ~5,000 → 3,435 (estaba sobrestimado) 4. pineal: 6 stubs (<30 LOC c/u), no 5. Export (23 LOC) también es stub funcional. LOC reales por sub-crate documentados. 5. init/SDD.md: ente-soma es wrapper de 44 LOC, no ~30. 6. akasha/SDD.md: fastembed está detrás de feature `embeddings`, ort es transitivo. Sin feature, akasha-nous-real es stub mínimo. 7. vista/barra: LOC ajustados (vista-core 177, barra-core 108). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
550c98f275 |
refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
86fb6ae20b |
feat(cosmobiologia-render): compose_wheel rico con palette + dial 3D + spread + coord labels
El render agnóstico ya no es un esqueleto — porta al WASM la mayoría de los detalles visuales que tenía solo el canvas gpui nativo: - palette.rs: Palette dark/light replicando AstroPalette del theme nativo, pero en Rgba (no Hsla de gpui). Métodos planet/aspect/sign para resolver color por id simbólico, + house_ring con hue-shift. - CompositionOpts extendido: palette, dial_3d, draw_ascensional_cross, show_coord_labels, show_minor_aspects. Defaults razonables. - compose_wheel ahora dibuja: background panel, dial 3D bevel (4 strokes concéntricos con alpha decreciente), subdivisiones cada 10° con sign boundaries reforzados, signos con color elemental, casas topocéntricas + geocéntricas en sus rings canónicos, cuerpos con spread anti-solapamiento + clusters + disco coloreado por planeta, coord labels "DD°MM'♈" en natal, aspectos con width inversa al orbe + filtrado opcional de minors, cruz ascensional dashed + pills ASC/MC/DESC/IC. - cosmobiologia-web: nuevo render_model_to_svg_themed(dark: bool) para que el cliente JS elija palette según preferencia del UA. Tests del módulo math siguen verdes (10/10). Smoke test del server: /api/sky.svg ahora emite 22 circles, 77 lines, 52 texts con paleta real (vs ~6 circles, 24 lines, 36 texts del esqueleto previo). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
4619ba3a2b |
feat(cosmobiologia): crate WASM + fallback inteligente + DEPLOY.md (fase 3b)
Cierra el requerimiento del módulo web. El cliente puede correr en modo WASM (render local, scrubbing instantáneo, sin round-trip) o caer al SSR (server compone el SVG) si el bundle WASM no está desplegado. Switch automático sin configuración. cosmobiologia-web (crate nuevo, cdylib + rlib): - `lib.rs` con un único export wasm-bindgen `render_model_to_svg(json, size, rot_offset_deg) -> String` que deserializa un `RenderModel`, llama `compose_wheel` + `draw_commands_to_svg` de cosmobiologia-render, y devuelve el SVG inline listo para `wheel.innerHTML = svg`. - Cargo.toml con `wasm-bindgen` + `getrandom` con feature `wasm_js` solo bajo `target_arch = "wasm32"` (en nativo no se arrastran). - `.cargo/config.toml` con `--cfg getrandom_backend="wasm_js"` para que la transitividad `uuid → cosmobiologia-model → cosmobiologia-render` compile a wasm32-unknown-unknown. - `cargo check -p cosmobiologia-web` pasa en nativo (valida la signature). Build WASM real lo dispara el usuario con `wasm-pack build --target web --out-dir ../../../apps/ cosmobiologia-server/static/wasm` — comando documentado en DEPLOY.md y en doc del crate. cosmobiologia-server — soporte cliente WASM: - Nuevo flag `--static-wasm <dir>` (default = static/wasm relativo al cwd). Si el directorio existe, los archivos WASM se sirven en `/static/wasm/*`. Si no existe, devuelve 404 y el cliente cae al SSR. - ServeDir de `tower-http` para fileserver simple. index.html: - Nueva función `tryLoadWasm()` que hace `import dinámico` del módulo WASM al boot. Si carga OK, `wasm` global queda set; si falla (archivo no existe o error de WASM), se loguea info y sigue. - `refreshSelected()` ahora hace fetch del RenderModel JSON (`/api/sky` o `/api/charts/:id/render`); si hay WASM, llama `wasm.render_model_to_svg(json)` localmente; si no hay WASM o el render WASM falla, hace fetch del SVG SSR como fallback. - Info row muestra "WASM" o "SSR" según el modo activo — visualmente claro qué pipeline está corriendo. cosmobiologia-server/DEPLOY.md (nuevo): - Build del binario + build del WASM (con wasm-pack). - systemd service template (sandboxing básico: ProtectSystem strict, ProtectHome, PrivateTmp, NoNewPrivileges). - Caddyfile y nginx para reverse proxy con TLS. - DNS: A records para cosmobiologia.gioser.net + api.*. - CORS: warnings sobre permissive vs producción multi-usuario. - Separación demo público (DB vacía en VPS) vs desktop personal (DB compartida en `~/.local/share/cosmobiologia/`). - Backup con SQLite `.backup`. - Smoke test post-deploy con curl. - Tabla de referencia de TODOS los endpoints. Tests: 10 verdes (cosmobiologia-render::math). El cliente WASM no agrega tests propios — la lógica testeable vive en render. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
eac8c58974 |
feat(cosmobiologia): cliente web demo SSR + DrawCommand agnóstico (fase 3a)
Fase 3a — render web operativo sin WASM. Demo funcional inmediata
con server-side rendering del SVG; el cliente WASM puro se hace en
fase 3b cuando wasm-pack / wasm-bindgen-cli esté instalado.
cosmobiologia-render — nuevo módulo `draw`:
- `Rgba { r, g, b, a }` color agnóstico (no Hsla, no hex CSS).
- `DrawCommand` enum tagged-serde: `Circle`, `Line`, `Text`. Listo
para WASM o nativo — solo primitivas.
- `CompositionOpts { size, rot_offset_deg, include_bodies }`.
- `compose_wheel(model, opts) -> Vec<DrawCommand>` primera versión:
anillo zodiacal (A+B), 12 cusps cada 30°, glyphs de signos,
corona de casas (C+D), cusps de casas (Asc/IC/Desc/MC con peso
doble), house numbers, anillo de aspectos (E), líneas de
aspectos coloreadas por kind, glyphs de cuerpos natales con
disco halo.
- `draw_commands_to_svg(cmds, size) -> String` serializa la lista
a SVG inline. SVG-escape, `text-anchor` configurable, `dominant
-baseline=central` para centrar verticalmente.
Pendiente en `compose_wheel` (extender en commits siguientes,
copiando lo del canvas gpui): spread anti-solapamiento, clusters
compartidos, coord labels, dial 3D bevel, vignette, themes
PrintColor/PrintBW. Por ahora es un MVP suficiente para verificar
end-to-end y para que el usuario tenga algo visible YA.
cosmobiologia-server:
- Nuevos endpoints:
* `GET /` → HTML del cliente (single-page)
* `GET /api/sky.svg` → SVG agnóstico del "cielo ahora"
* `GET /api/charts/:id/wheel.svg` → SVG agnóstico de carta con
overlays via query (offset,
transit, prog, sa, pd)
- Página HTML embebida (`include_str!` de `static/index.html`):
* Sidebar con tree (groups → contacts → charts), click selecciona
* "⏱ Cielo ahora" siempre disponible como botón rápido
* Toolbar con input offset minutos + checkbox tránsito + botón
refresh + botón download SVG
* Botones "Nuevo grupo / Nuevo contacto" con prompt + POST
* Wheel renderizado en SVG inline, info row con título/asc/mc/ms
Smoke test:
cargo run -p cosmobiologia-server -- --port 18787
curl / → HTML (página completa)
curl /api/sky.svg → 12 KB SVG con 17 circles +
51 lines + 36 texts
curl /api/tree → árbol JSON
curl POST /api/groups → crea grupo
Browser http://127.0.0.1:8787 → wheel visible
Próximo (fase 3b): cliente cdylib WASM `cosmobiologia-web` que
reemplace el SSR — recibe RenderModel JSON, llama compose_wheel +
draw_commands_to_svg en WASM, monta SVG via DOM. Trade-off: el
SSR de hoy es 12 KB transferidos por click (sólido); WASM
descarga ~150 KB una sola vez y luego compone localmente
(scrubbing instantáneo, sin round-trip al server).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d341004f59 |
feat(cosmobiologia-server): server HTTP single-user con CRUD completo (fase 2)
Crate nuevo `cosmobiologia-server` (binario axum, nativo) que
monta `cosmobiologia-engine` + `cosmobiologia-store` y expone
la rueda + el CRUD del tree por HTTP.
Endpoints v1:
- `GET /api/health`
- `GET /api/tree` tree completo anidado
- `POST /api/groups` crear grupo
- `PATCH /api/groups/:id` renombrar
- `DELETE /api/groups/:id` borrar
- `POST /api/contacts` crear contacto
- `PATCH /api/contacts/:id` renombrar
- `DELETE /api/contacts/:id` borrar
- `POST /api/charts` crear carta
- `GET /api/charts/:id` chart JSON
- `PATCH /api/charts/:id` editar (label/birth/config)
- `DELETE /api/charts/:id` borrar
- `GET /api/charts/:id/render` RenderModel JSON
- `GET /api/charts/:id/svg` SVG inline (reusa
svg_export del engine)
- `GET /api/sky` "Cielo ahora" — RenderModel
UTC actual sin chart_id real
Query params del render para activar overlays sin POST:
- `offset_min=<i64>` time scrubbing
- `transit=1` overlay de tránsito al now
- `prog_age=<f64>` progresión secundaria
- `sa_age=<f64>` solar arc
- `pd_age=<f64>` primary directions (Naibod)
Decisiones:
- Single-user, sin auth. Bind por default a `127.0.0.1:8787` —
el server NO debe exponerse a la red pública en esta fase.
- DB por default = misma del desktop (`$XDG_DATA_HOME/cosmobiologia/
charts.db`). `--db` permite override.
- CORS permissive (es localhost, single-user, sin auth).
- `ApiError` con mapeo a HTTP status: 404 NotFound,
400 BadRequest, 500 todo lo demás. Body JSON `{ "error": "..." }`.
Smoke test:
cargo run -p cosmobiologia-server -- --port 18787
curl /api/health → {"status":"ok",...}
curl POST /api/groups → {"id":"01KRYVP...","name":"Familia",...}
curl POST /api/contacts → {"id":"01KRYVP...","group_id":...}
curl /api/tree → árbol anidado
curl /api/sky → RenderModel con VSOP real
Pendiente (fase 3): cliente `cosmobiologia-web` (cdylib WASM)
que consuma estos endpoints y pinte SVG/Canvas2D.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
9b12958660 | merge: rename tahuantinsuyu → cosmobiologia | ||
|
|
06a1ca11ce |
chore: rename tahuantinsuyu → cosmobiologia
Rename clean del proyecto astrológico antes de empezar el módulo
web (fase 2 = server axum, fase 3 = cliente WASM). Hacerlo ahora
ahorra refactor de URLs, package.json, paths de assets HTML y
deploy configs que aparecerían con el nombre en cuanto exista el
server.
Mecánica:
- `git mv` de los 10 crates de módulo + 2 apps:
* `crates/modules/tahuantinsuyu/` → `cosmobiologia/`
* `crates/modules/tahuantinsuyu/tahuantinsuyu-*` →
`cosmobiologia/cosmobiologia-*`
* `crates/apps/tahuantinsuyu` y `tahuantinsuyu-cli` análogos.
- Sed sobre todos los `.rs` y `.toml`: `tahuantinsuyu` →
`cosmobiologia` (cubre crate names, deps paths, use
statements, ProjectDirs literals, binary names).
- Workspace `Cargo.toml`: members con paths nuevos.
- Memoria del proyecto (`~/.claude/.../memory/project_*.md`)
actualizada.
Cero leftovers: `grep -rn tahuantinsuyu --include="*.rs"
--include="*.toml" crates/` devuelve vacío.
DB & XDG: clean slate. La nueva app arranca con DB vacía en
`$XDG_DATA_HOME/cosmobiologia/charts.db`. Si tenías cartas
guardadas, viven todavía en `~/.local/share/tahuantinsuyu/` —
las podés migrar manualmente con un `cp`.
IDs UI inalterados: el prefijo `tts-` de gpui ElementIds queda
igual (cosmético, no afecta funcionalidad). Cambiarlo a `cb-`
ahora sería 3-4 líneas más de sed pero ningún beneficio
operativo.
Tests: 20 verdes (10 shell + 10 render math). Compila full:
`cargo check -p cosmobiologia` OK.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
9084cf4b79 |
refactor(tahuantinsuyu): extrae tahuantinsuyu-render — preparación para WASM
Fase 1 de "módulo web": extracción del modelo y la matemática
agnóstica de surface a un crate separado, sin dependencia de
gpui ni de eternal. Es la base sobre la que el cliente WASM y
el canvas nativo van a converger.
Crate nuevo `tahuantinsuyu-render`:
- Tipos del RenderModel migrados desde `tahuantinsuyu-engine`:
`RenderModel`, `Layer`, `LayerKind`, `Geometry`, `LineSeg`,
`PointMark`, `Glyph`, `OverlayMeta`, `UranianGroup`,
`AspectSummary`, `OUTER_RING_MODULES`. El engine los
reexporta — ningún call site del shell/canvas/modules/tree/
panel cambia su `use`.
- Módulo `math` con la geometría canónica del wheel migrada
desde `tahuantinsuyu-canvas`:
* `Radii` con los aros A/B/C/D/E + helpers `body_ring` y
`aspect_endpoints`
* `polar_to_screen` (Asc a las 9 del reloj)
* `spread_angles` (anti-solapamiento con damping + clamp por
glyph)
* `find_clusters` (con wrap-around)
* `format_coord_compact` ("DD°MM'{signo}")
- 10 tests del math (5 spread + 4 coord + 1 polar) viajaron con
las implementaciones. El canvas se queda solo con los tests
de UI.
Por qué un crate aparte:
- `tahuantinsuyu-engine` arrastra `eternal-sky` (VSOP2013 +
I/O de tablas) que NO compila a WASM sin empaquetar 30+ MB
de efemérides. Los tipos del modelo son serde puro y sí
compilan a WASM — extraerlos libera al cliente web futuro
de la dependencia transitiva.
- Cuando llegue la fase 2 (`tahuantinsuyu-server` axum) y la
fase 3 (`tahuantinsuyu-web` cdylib WASM), ambos consumen
`tahuantinsuyu-render` con la misma fuente de verdad sobre
el layout, evitando duplicar la lógica entre desktop y web.
Pendiente: `tahuantinsuyu-model` arrastra `uuid → getrandom`
que falla a WASM sin `wasm_js` feature flag. Lo resuelvo en la
fase del cliente WASM (necesita su propio Cargo.toml con la
config getrandom + .cargo/config con RUSTFLAGS).
Tests: 20 verdes (10 shell + 10 render math). Compilación
nativa OK; canvas sin cambios visuales (mismo código,
diferente origen).
|
||
|
|
8e95c884ed |
feat(tahuantinsuyu): "Guardar como…" en Tránsito y Progresada
Extiende el patrón de F4 a dos módulos más:
- **Tránsito**: nuevo `Control::Action "💾 Guardar tránsito como
carta libre"`. Captura el momento actual (UTC `now()`) anclado
a las coordenadas del natal. Label `{natal} transito · YYYY-MM-DD
HH:MM UTC`. Útil para "qué pasaba en el cielo de Pedro ahora
mismo, pegado como carta".
- **Progresada secundaria**: análogo, sufijo `prog-{N}a`. El
birth_data del Chart resultante es REAL (natal_instant + N días
simbólicos × 86400 s), así que cuando se computa de nuevo como
natal produce las posiciones progresadas correctas. El usuario
edita el slider, click → la carta queda guardada como libre
para después persistir.
Backend:
- Dos funciones nuevas en `tahuantinsuyu-engine`:
`compute_transit_chart(chart)` y
`compute_progression_chart(chart, age)`. Reusan
`parse_iso8601_components` (introducido en el commit del PR).
- En el shell: nuevo helper `insert_derived_free_chart(source,
birth, label)` que el callsite de PR ahora reusa (refactor
pequeño).
Sobre solar_arc y primary_directions:
- NO se agrega el botón. SA y PD son transformaciones matemáticas
puras — un Chart natal computado en el "momento dirigido" daría
posiciones distintas a las dirigidas (porque SA rota uniforme y
PD es función de RA, no de longitud eclíptica). Para guardarlas
haría falta extender `Chart` con un kind
`Derived { source, transform, params }` que el engine sepa
rehidratar al render. TODO en otra fase.
Tests: 10 verdes (sin cambios en los paths probados).
|
||
|
|
9db0591f28 |
feat(tahuantinsuyu): "Guardar como…" en módulo Retorno planetario (F4)
Cierra la fase B con el botón pedido por el usuario: tener una
carta natal abierta, activar el módulo Retorno planetario con
edad N + cuerpo (ej. Sol, 34 años), y al click guardar la carta
resultante con sufijo automático `rs-34` en el mismo contacto.
Infraestructura nueva (extensible a otros overlays):
- `Control::Action { key, label }` en tahuantinsuyu-modules —
un botón sin estado que el panel pinta como pill clickeable.
- `PanelEvent::Action { module_id, key }` que el panel emite
al click y el shell despacha.
- `render_action` en tahuantinsuyu-panel: pill con bg_button
+ hover + border. Wrap en Div plano para tipo coherente.
Backend (eternal-bridge):
- Nueva función pública `compute_planetary_return_chart(chart,
body, target_age_years, shift_days) -> (StoredBirthData,
instant_label)` en `tahuantinsuyu-engine`. Reusa el cómputo
ya existente del overlay: `next_return` + parser ISO-8601
para extraer year/mm/dd/hh:mm:ss del instant del retorno.
Hereda lat/lon/alt/TZ del natal — convención clásica del
Solar return en la ciudad de nacimiento.
Flujo en el shell:
- Handler `on_panel_action` despacha por `(module_id, key)`. Hoy
solo `planetary_return.save_as_free` está cableado; otros
módulos overlay (progression, solar_arc, primary_directions,
transit) son extensión natural — TODO.
- `save_planetary_return_as_free`:
1) lee config (body, age, shift_days) del module_configs
2) llama `compute_planetary_return_chart`
3) construye un `Chart` clonando el natal con birth_data
nuevo + label `{contacto} rs-34 · 2024-08-12 14:23 UTC`
(sufijo según cuerpo: `rs` para Sol, `lunar` para Luna,
nombre directo para los demás)
4) inserta como FreeChart con id `free-{N}` y la
selecciona para que el usuario la vea
- El usuario después puede usar el menú contextual de la
free chart para "Guardar como…" → modal F3 → persiste
bajo el contacto que elija (típicamente el del natal).
UX completa:
1. Tener natal abierta
2. Panel: módulo "Retorno planetario" → Activar + elegir
cuerpo + slider edad
3. Click "💾 Guardar retorno como carta libre"
4. La nueva carta aparece en "Cartas libres" seleccionada
5. Click derecho → "Guardar como…" → elegir contacto +
confirmar nombre
10 tests verdes.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
dd836522ab |
feat(tahuantinsuyu): editor inline para cartas libres (F2)
Las cartas libres ahora se pueden editar en su totalidad (fecha,
hora, lugar, lat/lon/alt, TZ, label) desde el menú contextual.
La edición es **in-memory** — la carta se queda como libre tras
el cambio; para persistirla hay que usar "Guardar como…".
Tree:
- Nuevo `Modal::EditFreeChart { source_id, form, error }`
paralelo al `Modal::EditChart` existente. Reusa la misma
`ChartForm` (11 TextInputs: name + place + date + time + TZ
+ lat/lon/alt) y la misma función `render_chart_form` para
pintarlo. El title cambia a "Editar carta libre".
- `open_edit_free_chart_modal(source_id, w, cx)`: lee el entry
de `self.free_charts` (que ahora trae `birth_data` además
de id+label), pre-puebla el form, y abre el modal.
- Submit: `build_chart_from_form` parsea + valida; al éxito
emite nuevo evento `TreeEvent::FreeChartEditConfirmed
{ source_id, birth_data, label }`. Al error, conserva el
modal con la pill destructiva.
- City picker funciona como antes — el branch de
`apply_city_preset` se extendió para que reconozca
`Modal::EditFreeChart` además de Create/Edit.
Modelo:
- `FreeChartEntry` ahora incluye `birth_data: StoredBirthData`
además de id+label. El shell se lo pasa al setter; el tree
lo usa para pre-poblar el form sin tener que pedirlo al
shell.
Shell:
- `push_free_charts_to_tree` clona `birth_data` en cada entry.
- Handler `FreeChartEditConfirmed`: actualiza
`free_charts[id]` con los nuevos datos + label, re-publica
al tree, y si la carta editada era la activa, re-renderea
el wheel.
Menú contextual de "Cartas libres" / `<carta libre>` ahora:
- Editar datos…
- Guardar como…
- Borrar (no se ofrece sobre sky-now)
10 tests verdes (sin afectar lo testeado).
Próximo y último: F4 — botón "Guardar como…" en cada módulo
overlay (RS, prog, sa, gr) que captura la carta derivada con
un sufijo automático en el contacto original.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
a83b0396ce |
feat(tahuantinsuyu): modal "Guardar como…" real para cartas libres (F3)
Reemplaza el `save_free_chart_quick` MVP de la fase A por un
modal completo que el usuario controla:
Tree:
- Nuevo `Modal::SaveFreeChart { source_id, name, new_contact_name,
selected_contact, all_contacts, error }`.
- `open_save_free_chart_modal` abre el modal pre-poblando `name`
con el label de la carta libre y `selected_contact` con el
primer contacto existente (o `None` = nuevo contacto si no
hay ninguno).
- `gather_all_contacts` recorre la jerarquía recursivamente
devolviendo `(ContactId, "Grupo / Subgrupo / Contacto")` —
el usuario ve la ruta completa, no solo el nombre.
- `render_save_free_chart` pinta:
* Input "Nombre" pre-cargado
* Lista de contactos como botones radio (● / ○) + opción
"Nuevo contacto…" al final
* Si "Nuevo contacto…" seleccionado, aparece input
"Nombre del contacto nuevo"
* Botones Cancelar / Guardar
- `set_save_modal_contact` alterna el radio sin recrear inputs.
- Validaciones: nombre de carta no vacío; si `selected_contact`
es `None`, exigir `new_contact_name` no vacío. Errores se
muestran en una pill destructiva dentro del modal.
- Submit emite nuevo evento `TreeEvent::FreeChartSaveConfirmed
{ source_id, chart_name, contact, new_contact_name }`.
Shell:
- `persist_free_chart` resuelve el contacto destino (existente
o crea uno nuevo), llama `store.create_chart`, y al éxito
remueve la carta libre del mapa (salvo `sky-now`, que es
persistente). Si la carta libre estaba seleccionada, vuelve
al Cielo. Refresca opciones del picker para que el dropdown
ChartPicker incluya la carta recién guardada.
- El handler `SaveFreeChartRequested` queda como hook vacío;
el menú del tree abre el modal directamente con `window`.
10 tests verdes (no se afectaron los paths probados).
Próximo: F2 (editor inline de fecha/lugar/hora de la carta
libre) y F4 (botón "Guardar como…" en cada módulo overlay
con sufijo automático).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
72da2934e8 |
feat(tahuantinsuyu): "Cartas libres" como sección + guardar a contacto (fase A)
Estructura de cartas no-persistidas con CRUD básico en la UI.
Modelo:
- `FreeChartId(String)` con sentinela `sky_now()` reservado para la
carta del cielo. Otros ids se generan al vuelo como `free-N`.
- `TreeSelection::FreeChart(FreeChartId)` y `FreeChartsRoot`
reemplazan al variante puntual `PresentSky` (que era un caso
especial paralelo).
Tree:
- Sección **"🜨 Cartas libres"** branch fijo al FONDO del tree
(al contrario de "◇ General" que va arriba). Contiene "Cielo
ahora" como primera leaf + cualquier carta libre creada.
Expandida por default.
- Menu contextual:
* sobre la sección: "Nueva carta libre" → `NewFreeChartRequested`
* sobre una carta libre: "Guardar como…" + "Borrar" (`sky-now`
no admite borrar)
- Setter `set_free_charts(Vec<FreeChartEntry>)` actualizado por
el shell tras cada mutación.
Shell:
- Nuevo state: `free_charts: HashMap<FreeChartId, Chart>` +
`next_free_id: u32`.
- `ensure_sky_now` inserta/refresca "Cielo ahora" contra el
reloj actual. Al boot se llama y la carta queda seleccionada.
- `push_free_charts_to_tree` publica la lista al tree
(sky-now primero, después los `free-N` ordenados).
- Handlers de los 3 nuevos eventos:
* `NewFreeChartRequested` → crea entry, selecciona
* `SaveFreeChartRequested(id)` → `save_free_chart_quick`
(MVP: crea contacto nuevo con el label de la carta + carta
bajo él; la fase B reemplaza por modal con dropdown)
* `DeleteFreeChartRequested(id)` → quita de free_charts;
si era la activa, vuelve al Cielo
10 tests verdes (sin cambios — la lógica nueva afecta paths que
no están cubiertos en los smoke tests actuales).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
72758e75ce |
feat(tahuantinsuyu): "Cielo ahora" + "General" como rows fijos al top del tree
Dos entradas siempre presentes en la cima del árbol:
1. **⏱ Cielo ahora** (leaf): selecciona una carta efímera del
instante actual en Greenwich (UTC, lat 51.4769°, lon 0°,
alt 47 m). NO se persiste en la store — `build_present_sky_chart`
la construye al vuelo con `Chart { id: Default::default(), ... }`
y birth_data tomado de `SystemTime::now()` via `unix_to_civil_utc`
(algoritmo Howard Hinnant, exacto y proleptic-Gregoriano).
La carta queda **seleccionada por default** al boot — el usuario
abre la app y ya está viendo el firmamento actual, incluso si
no tiene contactos cargados.
2. **◇ General** (branch): contenedor virtual para los contactos
sin grupo asignado (parent=None). Antes esos contactos
aparecían sueltos al nivel raíz; ahora viven dentro de
"General" y se ofrece como destino claro para "Nuevo
contacto" desde su menú. Click sobre General muestra
thumbnails de TODAS las cartas de esos contactos en el canvas.
Soporte en `TreeSelection`: dos variantes nuevas `PresentSky` y
`GeneralRoot`. `parse_row` reconoce los IDs sentinela `sky:now`
y `general`. El shell maneja ambos casos en `apply_selection`:
- PresentSky → set `current_chart` + render
- GeneralRoot → grilla de thumbnails
`MenuTarget::from_selection` mapea PresentSky/GeneralRoot →
MenuTarget::Root (mismo menú "Nuevo grupo / Nuevo contacto").
`unix_to_civil_utc` con 4 tests cubre: epoch (1970-01-01),
2024-02-29 (año bisiesto), pre-epoch (-1 → 1969-12-31), y
year 2000.
Total 10 tests verdes (6 anteriores + 4 nuevos del calendario).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ca5dd04176 |
feat(arje): reemplaza systemd en máquina real con coexistencia GRUB
Flujo seguro de adopción: arje se instala como entrada GRUB
alternativa, no toca systemd ni /sbin/init. Booteás arje cuando
querés, volvés a systemd si rompe (rollback instantáneo desde el
menú).
Artefactos nuevos:
- scripts/install-arje-as-init.sh: instala binarios musl-static a
/usr/sbin/ y /usr/bin/, copia seed a /ente/seed.card.json, agrega
menuentry "arje" a /etc/grub.d/40_custom usando init=/sbin/ente-zero
con kernel + initrd nativos. NO cambia GRUB_DEFAULT. Idempotente
(regenera el bloque ARJE-MENUENTRY si existe).
- scripts/uninstall-arje.sh: revierte binarios + menuentry. Conserva
/ente/seed.card.json por si la editaste.
- seeds/arje-host.card.json: seed para máquina real con 15 cards:
tmpfiles + mount-fstab + swap-on + dbus-system + 11 compat shims +
dhcpcd + sshd + agetty. Validada.
- docs/arje-replace-systemd.md: filosofía vs systemd ("no acapara
porque no genera, sólo arranca lo declarado"), lista exhaustiva de
servicios systemd que NO deben migrarse (ModemManager, snapd, cups,
unattended-upgrades, etc.), tabla diferencial de UX vs systemd
(systemctl restart → kill PID, systemctl enable → editar seed),
checklist pre-primer-boot, instrucciones de rollback y cómo hacer
arje default sólo cuando estés seguro.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e4b1d41b62 |
feat(arje): incluye CLIs admin en el initrd (brahman-status/busctl/brainctl)
Para "ver" la administración del init desde el shell que arrancó dentro
del initramfs hacían falta clientes que hablen los 4 sockets Unix. Los
exists como examples de cada crate; los empaquetamos ahora.
build-arje-initrd.sh:
- cargo build --example brahman-status -p brahman-admin
- cargo build --example busctl -p ente-bus
- cargo build --example brainctl -p ente-brain
- Copia los 3 a /usr/bin/ del initrd.
docs/arje-boot.md §6b reescrita con:
- Tabla de sockets corregida (defaults a /tmp/ cuando no hay
XDG_RUNTIME_DIR/TMPDIR, que es el caso en initrd).
- Cookbook de los 3 CLIs con ejemplos de sesión típica dentro de la VM.
- Nota para vendorear socat-static via EXTRA_BINS si querés conectar
crudo a un socket.
§1 layout actualizado con /usr/bin/{brahman-status,busctl,brainctl}.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ec458b8a6f |
fix(arje): build-initrd compila musl-static por default
Sin musl, PID 1 panic con "error while loading shared libraries: libgcc_s.so.1" porque el initramfs no incluye libgcc/glibc/ld-linux. Solución estándar: target x86_64-unknown-linux-musl produce un ELF totalmente estático. Cambios en scripts/build-arje-initrd.sh: - ARJE_TARGET=x86_64-unknown-linux-musl por default (override con env). - Chequeo del target instalado antes de buildear; mensaje accionable con los comandos exactos (rustup target add..., apt install musl-tools, etc.) si falta. - Sanity check con `file`: aborta si ente-zero quedó dinámico. - Sanity check para busybox: aborta si el BUSYBOX_BIN apunta a un binario dinámico (la otra causa #1 de panic). - BIN_DIR ahora apunta a target/$TARGET/release/. Docs (docs/arje-boot.md): - §2a explica el porqué de musl. - §2b lista requisitos del host (rustup target, musl-tools, busybox-static). - §7 sección nueva de troubleshooting con el síntoma exacto del libgcc_s panic + 3 escenarios comunes más. - Checklist pre-deploy actualizado con el chequeo de "statically linked". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
0e66cda079 |
fix(tahuantinsuyu): hit-test del hover usa display_deg post-spread
El hover hacía hit-test contra la posición REAL del planeta (`g.deg`) en vez de la posición de pintura (post-spread). Con clusters esto generaba que el cursor sobre el disco visible NO disparara el hover — había que apuntar al grado real (zona vacía) para activarlo. Fix: `on_hover_check` ahora corre el mismo `spread_angles` que `render_wheel` con los inputs equivalentes, y compara la posición del mouse contra `display_degs[i]` en lugar de `g.deg`. Nuevo helper `body_disk_base(module_id, kind, view_scale)` centraliza el cálculo del disco base — render y hit-test ambos lo usan, así no divergen si más adelante se ajusta el tamaño por tipo de capa. 11 tests verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e4882033cf |
fix(arje): validate.sh robusto frente a timeout 124 + pipefail
timeout 5 retorna 124 al matar ente-zero (dev mode termina solo). Bajo set -euo pipefail eso abortaba el script antes de chequear el log, así que build-arje-initrd.sh reportaba "seed inválida" aunque la Card estuviera bien. Cambios: - Capturamos el output a un archivo (no pipeline) con set +e alrededor del timeout, así el exit code no importa. - El check sigue siendo grep -q sobre "Tarjeta Semilla cargada y validada"; si falla, ahora imprimimos las primeras 40 líneas del log al stderr para debug. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
074d8bcbc8 |
feat(tahuantinsuyu): cluster shrink, label compacto, hover destacado con z-order
Cuatro ajustes finos al esquema visual de planetas natales/topo:
1. **Discos achicados en cluster**: glyphs en cluster compartido
(≥2 miembros) llevan un factor adicional `0.86×` sobre el
shrink residual. Visualmente quedan apenas más pequeños — al
estar pegados, achicar un poco evita la sensación de
"amontonamiento" sin perder el unicode.
2. **Pill compartida más chica + libre de "espacios negros"**:
- Cálculo del ancho ahora usa `text.chars().count()` (era
`text.len()` en bytes — los chars unicode astronómicos
cuentan 3 bytes c/u y inflaban el ancho).
- Mínimo de ancho bajado de `font*2.0` a `font*1.4` y
padding lateral reducido. Pills con 1-3 chars ya no llevan
"espacios en negro" que sobrescriben elementos vecinos.
- Font del label compartido normal bajado a 9.0×s (era 10);
el hovereado sube a 10×s. Diferencial claro.
- Label individual también bajó a 8.5×s.
3. **Hover destacado**: nuevo "hovered_idx" identifica el glyph
bajo el cursor (de `HoverInfo::Body`). El glyph hovereado se
pinta al FINAL del árbol DOM — queda con z-order encima del
resto. Border al color pleno (vs 0.85), disco 1.18× y font
1.12× para destacarlo.
4. **Label del cluster hovereado destacado**: el cluster que
contiene al planeta bajo el cursor se renderiza con `fg_text`
(vs `fg_muted` para los demás) y font un punto más grande.
11 tests verdes (sin cambios — los affectados son del path de
render, no del cómputo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
121f19b915 |
fix(tahuantinsuyu): spread acotado + physics damping + label compartido por par
Tres problemas reportados sobre la pasada anterior: 1. **Planetas pisándose**: el "spread por centroides" dejaba a los miembros de cada cluster en sus posiciones REALES, así que pares en conjunción cerrada (5° real, disk ≈ 10°) seguían con discos solapados. Solución: spread directo sobre TODOS los glyphs, no solo sobre centroides. 2. **Empuje propagado a planetas lejanos** (era el motivo original de tirar el "spread directo"): ahora controlado con un **cap por glyph**: `max_shift_deg`. Ningún display puede alejarse más de `disk_angular` grados de su raw — un cluster denso no "empuja" a planetas que estaban lejos. El residual sube cuando el cap impide alcanzar el min_sep, y los discos se encogen. 3. **Algoritmo greedy oscilaba**: el empuje aplicado par-a-par reordenaba los displays a mitad de la pasada y nunca convergía (`tight_cluster_gets_spread` terminaba con 6.5° de diff cuando se pedían 10°). Reemplazado por **physics-step**: se acumulan las fuerzas de todos los pares en una pasada, se aplican con `damping = 0.6`, se clampea cada display al rango ±max_shift. 80 iteraciones convergen siempre. 4. **Labels repetidos en pares cercanos**: el threshold del cluster compartido era min(4°, disk_angular*0.5). Para discos de 10° angular, eso daba 4° — dos planetas a 5° formaban clusters separados, cada uno con su pill diciendo casi lo mismo. Subido a `disk_angular * 1.2` → pares a <12° comparten label. Nuevos tests: - `shift_is_bounded`: con max_shift=2°, ningún glyph se aleja más. - `distant_planet_unaffected_by_dense_cluster`: cluster denso en 100° + planeta solo en 200° → el de 200° se queda a <5° de raw. Total 11 tests verdes (6 spread + 5 coord). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
a0f67fd86f |
fix(tahuantinsuyu): spread no propaga + label lejos del disco + glyph legible
Tres bugs en la pasada anterior de anti-solapamiento:
1. **Empuje propagado en cadena**: el spread greedy aplicado a
cada glyph movía planetas lejanos cuando un cluster denso los
empujaba. Ejemplo reportado: planetas a 9° y 10° (conjunción
real) terminaban moviéndose hacia el 26° por la propagación
simétrica del empuje.
Solución: spread en dos pasos.
* `find_clusters` con threshold `min(4°, disk_angular*0.5)`
agrupa solo los que realmente están en conjunción cerrada.
Dentro del cluster los glyphs SE QUEDAN en sus pos reales —
dos planetas a 1° se ven a 1° (sus discos se rozan, refleja
la geometría astrológica).
* `spread_angles` se aplica SOLO a los **centroides** de los
clusters, con threshold = ancho angular del disco. El
empuje queda contenido a la vecindad inmediata; planetas
lejos del cluster no se mueven.
* Cada glyph hereda el shift de su cluster (centroide
displayed − centroide real, wrap a ±180°).
2. **Label pisaba al planeta**: `label_r = ring - disk*0.7`
dejaba solo ~2 px entre el borde del disco y la pill. Movido
a `ring - disk*1.3` para individuales y `ring - disk*1.5`
para clusters compartidos. Gap visual ~12 px.
3. **Símbolo se perdía en clusters densos**: shrink agresivo
(0.45 sobre residual) achicaba el font por debajo del
umbral legible del unicode astronómico. Bajado a 0.30, piso
del shrink subido a 0.60×, y piso absoluto del font a 11 px.
4. Threshold de label compartido bajado a ≥2 miembros (era ≥3).
En astrología, dos planetas en conjunción ya cuentan como un
stellium funcional y se beneficiarían del label combinado.
Tests: 10 verdes (5 spread + 5 coord).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
8592bab19e |
docs(arje): organiza core/ + seeds canónicas + boot reproducible
- crates/core/README.md: agrupamiento lógico de los 31 crates absorbidos
de arje (ente-*) y del protocolo brahman (brahman-*) en 6 grupos —
Init/PID 1, contratos, discovery, IPC+CAS, cerebro, 14 shims compat
systemd. No se movieron crates físicamente (rompería paths
cross-workspace).
- seeds/arje-minimal.card.json: PID1 + /bin/sh, smoke test QEMU.
- seeds/arje-prod.card.json: PID1 + 14 shims compat + tmpfiles/binfmt
one-shots + echo + getty (16 children). Validados con
brahman_card::Card::validate.
- seeds/validate.sh: carga la seed vía ente-zero en dev mode.
- scripts/build-arje-initrd.sh: empaqueta CPIO+gzip newc layout
/init→/sbin/ente-zero, /usr/sbin/ente-*-compat, /ente/seed.card.json,
/bin/{sh,...} (busybox o glibc+ldd). Tested: produce 20 MB initrd OK.
- scripts/run-arje-qemu.sh: qemu-system-x86_64 con KVM auto-detect,
-kernel/-initrd/-append "rdinit=/init console=ttyS0,115200 panic=10".
- docs/arje-boot.md: doc end-to-end — layout initramfs, QEMU (con kernel
del host o externo), GRUB bare metal, Proxmox/libvirt args:, schema
de Card con todas las validaciones, debugging (sockets de
introspección, snapshot/restore, metrics), checklist pre-deploy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
a92fa15777 |
feat(tahuantinsuyu): anti-solapamiento de glyphs + selector Naibod/Ptolomeo
Tres mejoras de UX para manejar conjunciones (stelium) y dar más control sobre el sistema GR: 1. `spread_angles(angles, min_sep_deg)`: reposiciona angularmente los glyphs adyacentes para que ningún par caiga más cerca que el threshold visual (derivado del ancho del label pill al radio del ring). Iterativo (≤60 pasos), re-ordena cada iteración para preservar el orden circular, devuelve también `residual` ∈ [0,1] = fracción de presión no resuelta. Las posiciones REALES no se tocan — solo afecta la geometría visual del glyph. 5 tests cubren: empty, separados intactos, cluster cerrado, orden preservado, cluster infactible. 2. Aplicación al render de Bodies (natal/topo/pd/outer): cada layer pasa por spread_angles antes de iterar glyphs. Si residual queda alta, los discos y fonts se encogen proporcionalmente (0.55..1.0×) y los coord labels se omiten — evita pillas montadas sobre el bloque. 3. `find_clusters(angles, threshold_deg)`: detecta grupos angularmente cercanos (incluye wrap-around 359°→1°). Glyphs en cluster de ≥3 miembros NO llevan coord label individual; en su lugar, al final del loop se pinta UN solo label compartido con los símbolos concatenados (ej. "☉ ☿ ♀ 14°56'♈") posicionado en el centroide angular del cluster. El usuario sigue viendo cada planeta con su disco, pero no se ahoga en pills superpuestas. 4. Selector Naibod/Ptolomeo en PrimaryDirectionsModule via `Control::Select`. Default Naibod (0°59'08.33″/año, moderno). El shell extrae `module_configs["primary_directions"]["key"]` y lo pasa en `PipelineRequest::PrimaryDirections { key }`; el bridge mapea string → `DirectionKey` y pasa al cómputo. El overlay meta muestra qué clave se usó: "GR Direcciones · 30.5a · Naibod". Tests: 16 verdes (6 shell + 5 spread + 5 coord). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
a7214e0498 |
refactor(tahuantinsuyu): aros a/b/c/d/e canónicos + un solo anillo por bloque
Reorganización de los radios siguiendo nomenclatura clara del usuario (de afuera hacia adentro): Aro A (1.00·r) externo zodiaco Zona AB signos ♈♉♊… (sign dial) Aro B (0.92·r) interno zodiaco / externo bloque ascensional Zona BC casas topo (cusps b→c) + planetas topo + coords Aro C (0.78·r) separador ascensional / casas geo Zona CD casas geo (cusps c→d) + sus coords Aro D (0.62·r) externo planetas natales (junto a D) planetas natales + coords Aro E (0.49·r) anclaje invisible de líneas de aspecto Overlays opcionales (transits, midpoints, progression, solar arc, composite) ahora viven todos INTERIORES al aro E — solo se pintan cuando su módulo está activo, así no compiten con el layout base. Cambios concretos en Radii: - Doc `Radii` reescrito con la nomenclatura a/b/c/d/e arriba. - Eliminado `bodies_inner` (la idea del "carril doble" confundía con el sistema de casas; ahora hay un único anillo por bloque). - Coord labels uniformes — `label_r = ring - disk_size * 0.7` (hacia adentro) tanto para natal como para topocéntrico, ya que cada bloque tiene su propia zona radial bien definida. - Coord pills de cusps de casa ahora se posan dentro de su propia zona (`r_in + (r_out - r_in) * 0.18`) — no se salen del bloque. - Stroke 3D del bloque de planetas natales se mueve a `houses_inner` (= aro D), que es el verdadero borde visible del cinturón. Si el usuario quiere un anillo adicional para algo en particular (p. ej. transits clásico afuera del zodiaco), se agrega cuando ese módulo se active. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
7eb620aa17 |
feat(tahuantinsuyu): GR dual-ring + topo ascensional pegado al dial + coords
Dos cambios mayores que cierran el sistema GR/ascensional:
1. Reordenamiento radial — la capa ascensional (topocéntrico
Polich-Page) se ubica AHORA pegada al sign dial, y la
geocéntrica clásica queda más adentro. Layout outer→inner:
- sign_dial (1.00 → 0.88)
- topo_houses_outer (0.875) / topo_houses_inner (0.79) ← P-P pegadas al zodiaco
- topocentric (0.755) ← planetas topo con coords
- transits (0.71)
- houses_outer (0.66) / houses_inner (0.54) ← Placidus geo
- midpoints (0.50) / bodies (0.47) / bodies_inner (0.44) ← natal geo con coords
- pd_direct (0.495) / pd_converse (0.425) ← dual-ring GR
- aspects (0.41) / progression (0.36) / solar_arc (0.30)
Topocéntrico default ON (era OFF en la fase previa).
Coord labels ahora se pintan también en planetas topocéntricos
(label hacia adentro, no afuera, para no chocar con casas P-P).
2. Sistema GR Direcciones Primarias (dual-ring):
- Nuevo `PipelineRequest::PrimaryDirections { target_age_years }`.
- `build_primary_directions_overlay` proyecta cada cuerpo natal
con `directed_longitude` (key Naibod) en dos direcciones —
directa y conversa — y emite dos Layer Bodies con
`module_id` "pd_direct" / "pd_converse".
- Canvas: nuevos `pd_direct` y `pd_converse` en Radii; en el
render de Bodies disco más chico y alpha 0.80. Los dos anillos
se marcan con punteado fino que "abraza" el cinturón natal
por afuera y por adentro — el natal queda en el centro.
- Nuevo `PrimaryDirectionsModule` con toggle + slider de edad
(0..120, step 0.05a). Activable desde el panel.
Tests: 6 shell + 5 coord siguen verdes; el motor matemático
(eternal-astrology directed_longitude) y house system Polich-Page
están testeados desde el commit `e385ab2` en eternal.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
1d49b9ff88 |
feat(tahuantinsuyu): capa "ascensional" topocéntrica completa
5 fases que cierran el sistema topocéntrico end-to-end, conviviendo
con el cómputo geocéntrico tradicional sin reemplazarlo:
T3 — Pipeline en tahuantinsuyu-engine:
- Nuevo `PipelineRequest::Topocentric`.
- `build_topocentric_overlay(natal, render)`: para cada placement
natal aplica `topocentric_ecliptic` (paralaje horizontal con
`distance_km/AU` + observer.lat_rad + LST + obliquidad), emite
Layer Bodies en ring=0.50 con `module_id="topocentric"`.
Recalcula cusps con `Houses::compute(PolichPage, ...)` y emite
Layer Houses asociado. Si la latitud cae en el círculo polar y
Polich-Page diverge, sigue con planetas topocéntricos solos.
T4 — Render overlay en canvas:
- Nuevo `Radii.topocentric = 0.555·r` (justo bajo el carril natal
bodies=0.60). `body_ring("topocentric")` lo mapea.
- Glyphs topocéntricos con disco más chico (22→22*s) y alpha 0.75
(vs 1.0 natal) — se distinguen como "el sutil debajo del
fuerte". En Luna el shift natal↔topo es visible; en Saturno los
dos glyphs casi se superponen.
- Cusps Polich-Page pintadas como línea punteada (dash 3/2.5px)
en un anillo interior al de casas geocéntricas, color
`house_cusp` α=0.55 — claramente sistema secundario sin
esconderse.
T5 — Módulo TopocentricModule:
- Nuevo módulo en tahuantinsuyu-modules con id="topocentric",
label "Topocéntrico (ascensional)". Toggle "Activar" default
OFF (es overlay opcional). Registrado en `Registry::with_builtins`.
- Shell traduce `module_configs["topocentric"]["enabled"] = true`
→ `PipelineRequest::Topocentric` en `build_requests`. Persiste
por carta vía el mismo mecanismo de `persist_module`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
86c5fd8653 |
feat(tahuantinsuyu): coord labels con minutos + control en panel
- Precisión a minutos: `format_coord_compact` ahora emite
"DD°MM'{signo}" (ej. "14°56'♈"). Trabaja en minutos enteros para
evitar drift de floats acumulado, hace rollover correcto a través
de bordes de signo (29°60' → 0° del siguiente) y wrap-around de
ángulos negativos. 5 tests verdes:
* 0° → "0°00'♈"
* 14.9333° → "14°56'♈"
* 29.9995° → "0°00'♉" (carry-over)
* 270° → "0°00'♑"
* -10° → "20°00'♓" (wrap)
- Toggle en panel: nuevo `Control::Toggle` "Coordenadas (grado°min')"
en NatalModule, default ON, hotkey C. Sincronización bidireccional:
panel → canvas via `set_show_coords` (idempotente, no emite),
canvas → panel via nuevo evento `CanvasEvent::ShowCoordsChanged`
que el shell traduce a `panel.set_toggle("natal","show_coords",…)`.
Sin loop porque el setter no emite.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
8ede06f8c4 |
feat(tahuantinsuyu): distinción eclíptica/casas, coord labels, jog-dial con Ctrl
- Anillo de casas claramente distinto del dial zodiacal: nuevo
`house_ring_color(palette)` que toma `house_cusp` y le aplica
un hue shift de 140° en paletas con color (en BW devuelve el
color original — un shift cromático en monocromo es ruido sin
información). El sistema ascensional (casas) ya no se confunde
con el eclíptico (signos): dorado vs verde/teal.
- Coordenadas permanentes en planetas y cusps de casa: por
default visibles, togglean con hotkey `C` o desde el panel
vía `state.show_coords`. Cada planeta natal lleva una pill
pequeña afuera del disco con "DD°{signo}" (ej. "14°♈"); cada
cusp de casa lleva la misma pill por dentro del anillo
interior. Helpers nuevos: `format_coord_compact`,
`coord_label`.
- Jog-dial requiere Ctrl/Cmd para activar. Sin modifier, LMB
drag es siempre pan — sobre o fuera del anillo. Con modifier
+ LMB sobre el anillo se activa la rotación de tiempo (el
control de rectificación). Evita rotaciones accidentales al
navegar la rueda.
- Hint del info_row actualizado: incluye [C]oords y la
convención "Ctrl+drag = tiempo".
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e9369371db |
fix(tahuantinsuyu): crash al abrir modal + simplificación de anillos
- Crash fix (panic en gpui entity_map.rs:138 / double_lease): `render_chart_form` hacía `cx.entity().read(cx)` mientras estaba dentro del `render()` del tree — la entity ya estaba leased como `&mut self` y un read concurrente disparaba el double_lease_panic. Se cambió la firma para recibir `picker_open` y `city_atlas` como parámetros desde `render_modal` (que sí tiene `&self`). - Simplificación de anillos: el carril de planetas se acerca (bodies 0.60·r / bodies_inner 0.57·r) — antes 0.05 de separación, ahora 0.03, se ve como "carril" en lugar de dos anillos sueltos. El stroke visible del círculo de aspectos se elimina — `radii.aspects` queda solo como punto de anclaje para las líneas. El `bodies_inner` cambia a stroke plano más sutil (no 3D) para no competir con `bodies`. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
9acdf68d67 |
feat(tahuantinsuyu): orden visual — zoom uniforme, círculo de aspectos, profundidad
Tercera tanda de UX a partir de feedback: - Zoom uniforme sobre glyphs DOM: font_size y disk_size de signos, números de casa, planetas natales/overlay/outer y labels ASC/MC/DESC/IC se multiplican por view_scale. Antes solo escalaba la geometría del canvas (anillos, líneas), los símbolos quedaban fijos — sensación de "todo se mueve menos los iconos". - Doble anillo de planetas + círculo de aspectos: nuevo `bodies_inner` en `Radii`, junto con `bodies` define el "cinturón" donde viven los glyphs natales. `aspects` movido de 0.24*r a 0.49*r (de cerca-del-centro a pegado al cinturón) — las líneas de aspecto ahora conectan cuerpos cerca de su anillo en lugar de cruzar toda la rueda. Los tres anillos (bodies, bodies_inner, aspects) se pintan con stroke_circle_3d para que sean visibles. - Doble línea de casas más fuerte: houses_outer + houses_inner ambos con stroke_circle_3d y `house_cusp` α=0.85. Antes solo houses_inner tenía un stroke plano y débil. - Líneas de aspecto por orbe + filtro de menores: `aspect_width(kind, orb, mono)` modula grosor inverso al orbe. Aspectos mayores arrancan en techo 2.1 px (orbe 0°) hasta 0.7 px (orbe 8°); menores entre 0.5 y 1.2 px sobre orbe 0-3°. Los aspectos menores se omiten directamente si orbe > 3°. - Vignette en lugar de starfield: `paint_depth_field` reemplaza `paint_starfield`. Pinta ~28 anillos concéntricos del centro al borde con alpha cuadrática creciente (curve t²) — el centro permanece claro y el borde se oscurece. Da profundidad sin ruido de puntos. Solo en dark themes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
1078e433f2 |
feat(tahuantinsuyu): rueda 3D, hover-highlight, universo, themes papel
Segunda tanda de UX a partir de feedback de uso real: - Zoom/pan reasignados: wheel = zoom puro (sin modifier). LMB drag fuera del anillo de signos = pan; sobre el anillo = jog-dial (rectificación). MMB sigue como pan secundario. Tecla `0` resetea zoom + pan. - Planetas legibles: el "dot rellenado" se reduce a 3 px (solo marca el grado exacto). Encima va `planet_glyph` con disco-halo del bg_panel y border del color del planeta — el glyph unicode astronómico (☉☽☿♀♂♃♄♅♆♇) ahora se lee contra cualquier fondo. - Aspectos hover-highlight: al hovear un planeta, sus líneas se mantienen al 100 % y el resto cae a 18 %. Resuelve el "¿quién contra quién?" sin desordenar la rueda. - Ascensionales: cruz completa ASC-DESC + MC-IC (4 radios) con α=0.55. Labels ASC/MC/DESC/IC como pills con bg-halo y border `angle_highlight`, font 11 — antes eran texto chico que se fundía con el dial. - Universo: el wheel pierde su bg de cuadrado (que cortaba contra el panel). El root del canvas pinta un starfield sutil ~130 puntos deterministas (xorshift32 con seed fija, sin parpadeo entre frames). Solo activo en themes dark — sobre fondos claros generaría ruido. - Estilo 3D anillos: `stroke_circle_3d` (highlight +luma + base + shadow -luma) reemplaza al stroke plano en sign_outer, sign_inner y el outer ring. Más `paint_dial_bevel` con 10 strokes finos en bell curve entre sign_inner y sign_outer — simula gradient radial que gpui canvas no soporta nativo. - Theme `Print Color`: papel crema, paleta astro con luminancia 0.26-0.34 y saturación alta, sin glow ni gradients. - Theme `Print B&W`: monocromático sobre blanco puro. Aspectos diferenciados por dash pattern en lugar de color: conjunction/opposition sólidos, square dash medio, trine dash largo, sextile dotted, minors dotted finísimo. `paint_segment` con `dash: Option<(on,off)>` para implementar dashes (gpui canvas no tiene stroke dash nativo). Todos los tests siguen verdes (6 shell + 5 yahweh-theme + 2 tahuantinsuyu-theme). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e09207b152 |
feat(tahuantinsuyu): UX pass — splitter, light wheel, scroll, zoom/pan, dock lateral
Seis fixes derivados de testing real, ordenados por costo:
- Splitter (yahweh-widget-splitter): `flex-basis: 0` por item para que
el ratio flex-grow se respete sin importar el min-content de los
hijos. Sin esto, al cambiar el canvas de Empty→Wheel (WHEEL_SIZE
fijo de 580px) la suma de basis excedía el contenedor y flexbox
abandonaba el ratio 1:4, aplastando el tree a 0px (síntoma
reportado: "el tree desaparece al seleccionar carta"). También se
amplió la hit-zone del divider de 4px a 12px manteniendo una franja
visual de 4px centrada — la zona de pointer-capture y cursor es
ahora mucho más generosa, el visual sigue fino.
- Light mode wheel (tahuantinsuyu-canvas + tahuantinsuyu-theme): el
gradient del fondo del wheel pasa de alphas 0.06/0.03 (invisibles
contra fondo claro) a 0.18/0.10 cuando el theme es light. Cusps y
aspectos secundarios del light palette bajan luminancia y suben
alpha para no lavarse contra blanco.
- Panel scroll (tahuantinsuyu-panel): body del control panel agrega
`flex_grow + min_h(0) + overflow_y_scroll` para que cuando los
controles no caben aparezca scroll vertical en lugar de cortarse.
- Canvas zoom + pan (tahuantinsuyu-canvas): nuevo estado
view_scale / view_pan_x / view_pan_y. Ctrl+wheel zoomea
multiplicativo (clamp 0.5..3.0); wheel solo paneja. MMB drag para
pan libre. Hotkey `0` resetea zoom+pan. Hit-tests del jog-dial y
hover derivan ahora el `r_outer` del width actual del canvas, así
se autoescalan con el zoom.
- Panel dock lateral (shell.rs): nuevo `PanelDock { Bottom, Right,
Left }` configurable desde 3 botones en el header (◧ ▭ ◨). Bottom
mantiene el layout histórico (tree+canvas / panel); las variantes
laterales colapsan los splitters anidados en uno solo horizontal
de 3 columnas. El dock se persiste en `layout.panel_dock` y cada
layout guarda sus flex en una key distinta para no pisarse.
`load_split_flex_n` / `save_split_flex` generalizados a N hijos.
Tests: 6 pasan (incluye nuevo roundtrip de PanelDock y N-flex).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
d2b6b8b12e |
test(tahuantinsuyu): tests de integración del Shell con TestAppContext
Cubre los wiring points del binario que las unit tests por-crate no
ven: construcción end-to-end de Shell, selección de carta, derivación
de PipelineRequests y NatalOptions desde module_configs, y roundtrip
de layout via la tabla settings.
- `gpui` con `test-support` en dev-dependencies.
- 5 tests en `shell::tests`:
* `shell_constructs_smoke` — instancia Shell con store in-memory
sin panic. Cubre cableado de suscripciones (tree/panel/canvas
+ 2 splitters) y arranque del background loop del broker.
* `select_chart_updates_current` — apply_selection(Chart(id))
puebla `current_chart` y avanza `render_seq`.
* `module_toggles_produce_requests` — al habilitar 3 módulos
overlay, `build_requests` devuelve esos 3 PipelineRequest en
orden; deshabilitar uno lo remueve.
* `natal_options_read_from_configs` — orb_multiplier, show_minors,
show_dignities se leen correctamente desde module_configs["natal"].
* `split_flex_round_trip_via_store` — load/save_split_flex con
settings, incluyendo defaults para valores corruptos o ≤0.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e044d47516 |
feat(tahuantinsuyu): persistir flex de los splitters entre sesiones
Hasta ahora cada boot reseteaba los splitters al default (1:4 horizontal, 4:1 vertical), forzando a rearrastrar manualmente cada vez. Ahora el flex se guarda en la tabla `settings` ya existente. - `tahuantinsuyu-store`: nuevos `get_setting`/`set_setting` con upsert + test de roundtrip. - `tahuantinsuyu` shell: al boot, `load_split_flex` lee `layout.main_split` y `layout.outer_split` (formato "a,b" como texto). Si no hay entry o está corrupto cae a defaults. - Subscribe a `SplitEvent::DragEnd` en cada splitter — `save_split_flex` escribe los flex actuales al settings. Mouseup-driven, no cada-frame: 0 escrituras durante el drag, 1 al final. `module_configs` ya estaba persistido por carta vía la tabla `module_state` (`persist_module` + `load_persisted_module_states`), no requiere cambios. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
904f334069 |
perf(tahuantinsuyu): LRU cache de NatalChart por (birth, config, offset)
`NatalChart::compute` cuesta varios ms (VSOP2013 + casas + aspectos base). Bajo drag de slider en el panel, el shell dispara `compose()` decenas de veces — la natal del sujeto principal y la del partner de Synastry/Composite son **idénticas** entre frames pero hoy se recomputan. Nuevo `natal_cache.rs`: LRU de 8 entradas con `Mutex<Vec<(Key, Arc)>>`, key = hash de contenido `(StoredBirthData, StoredChartConfig, offset_minutes)`. Move-to-front en hit, evict del back cuando se llena. f64s se hashean vía `to_bits()`. `compute_natal_chart` ahora consulta el cache antes de delegar a eternal; firma cambia a devolver `Arc<NatalChart>` — los call sites (natal principal, partner de Synastry/Composite) usan auto-deref a través de `Arc::Deref` sin cambios. Editar una carta (cualquier campo de `StoredBirthData` o `StoredChartConfig`) invalida automáticamente su entrada porque el hash cambia. Capacidad 8 cubre el caso típico (natal + partner) con holgura. Test nuevo `natal_cache_hits_are_faster` valida que `compose` con offset_minutes repetido es más rápido que con offset distinto (HIT vs MISS): 9 tests engine, todos verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
2192c29d4f |
chore(tahuantinsuyu): fase 28 — limpieza de warnings y dead_code
- Reemplaza `Context<Self>` por `Context<'_, Self>` (y la misma fórmula para `Context<TahuantinsuyuTree>`) en tree/panel/canvas: 60 warnings de "hidden lifetime parameters are deprecated" → 0. - Borra `TREE_WIDTH` y `PANEL_HEIGHT` (constantes muertas) y el campo `main_split` del shell (vive como child de outer_split, no necesita retención aparte). - Quita `yahweh-bus` de tahuantinsuyu — el `bus: Entity<AppBus>` estaba con `#[allow(dead_code)]` sin cablear. Cuando lo necesitemos para coordinación cross-app lo reagregamos. - Suprime imports `Module` (panel), `AppContext` (canvas) y prefija el `cx` no usado en `on_jog_down`. - Marca `BrahmanStatus::Offline.reason` y `Shell.tree` con `#[allow(dead_code)]` documentando por qué se retienen (logs y subscripciones). Workspace ahora compila limpio salvo un warning conocido de `eternal-validation` (variable `sin_i` sin usar — fuera de brahman). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
e2da24239e |
feat(tahuantinsuyu): fase 26 — data plane brahman real (service socket + CLI)
Cierra el círculo del Card: el flow.input "chart-request" y output
"chart-result" declarados desde fase 1 ahora tienen data plane real.
Otros módulos brahman (incluyendo el CLI nuevo) pueden conectar al
Unix socket de Tahuantinsuyu y pedir cómputos de cartas natales sin
abrir la GUI.
## Protocolo y server
Nuevo módulo `tahuantinsuyu_card::service` con:
- `ComputeRequest { Ping, Natal { birth, config, offset_minutes,
label } }` — postcard-serializable
- `ComputeResponse { Pong, Render { render }, Error { message } }`
- `serve(socket_path)` — async loop sobre tokio::UnixListener,
spawn por conexión, frame `u32 length` LE + postcard payload
(mismo molde que brahman-handshake). Cap defensivo a 1 MiB por
frame.
- `request(&socket, &req) -> Response` — cliente async one-shot
(abre, envía, recibe, cierra)
- `spawn_service_thread(path)` — thread dedicado con tokio
current_thread runtime; loggea warn si bind falla, la app sigue
standalone.
La Card ahora declara `service_socket: Some(default_service_socket())`
— el broker brahman puede revelar este path a consumidores que
matcheen el flow `chart-request`. Path canónico:
`$XDG_CACHE_HOME/tahuantinsuyu/service.sock` (con fallback a
/tmp).
## CLI nuevo
`crates/apps/tahuantinsuyu-cli` — binario standalone que usa el
helper cliente. Comandos:
- `tahuantinsuyu-cli ping` — health check
- `tahuantinsuyu-cli natal --year ... --month ... --day ... --hour
... --minute ... --tz-min ... --lat ... --lon ... [--alt ...]
[--label "..."] [--offset-minutes N]` — pide compute y emite
RenderModel como JSON pretty-print en stdout
Útil para:
- Smoke tests del data plane (CI puede levantar la app + ping)
- Scripts batch (computar 100 cartas y exportar JSON)
- Integraciones con otros tools del fractal brahman vía broker
## Cambios accesorios
- apps/tahuantinsuyu/main.rs: spawn_service_thread al boot junto al
sidecar. Loggea el path del socket a stderr para debug.
- Cargo workspace: agrega tahuantinsuyu-cli como member.
- tahuantinsuyu-card Cargo: agrega deps (engine, model, postcard,
tokio, tracing, directories, thiserror) para soportar el server.
Lo que falta para integración brahman 100%:
- Suscripción al broker como "consumer-aware" para detectar cuando
otros módulos publican `chart-request`s
- Publishing de eventos al broker cuando se crean/borran cartas
- Ambos requieren protocolo handshake bidireccional sobre el Init
socket (no service_socket) — fase posterior.
cargo check verde, 8 tests engine + 1 modules verdes. CLI compila;
prueba end-to-end (ping + natal) queda a manos del usuario que
levante la GUI.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
6191ce7dee |
feat(tahuantinsuyu): fase 25 — production polish (tree search + hotkey SVG + aspect tooltips)
Tres mejoras de usabilidad que aprovechan toda la infraestructura ya construida. PNG export (#17) lo dejé para una fase futura — agregar resvg suma ~1MB al binario y el SVG ya cubre el caso profesional. ## #9 — Búsqueda en el tree Arriba del árbol de groups/contacts/cartas aparece un TextInput con placeholder "Buscar nombre…". Al apretar Enter aplica el filtro: case-insensitive substring match sobre group.name, contact.name y chart.label. Auto-expande recursivamente todos los ancestros que contienen un match — los resultados quedan visibles sin que el usuario tenga que abrir chevrons. Escape limpia el filtro. - tree: TahuantinsuyuTree gana `search_filter: String` y `search_input: Entity<TextInput>`. set_search_filter() actualiza + auto_expand_matches + refresh. - group_has_match() / contact_has_match() recursivos chequean si algún descendiente matchea. - append_groups/contacts/charts filtran por substring antes de emitir cada row. - render: nueva barra search arriba con border-bottom. ## #10 — Hotkey [S] = export SVG El canvas ya tenía botón "⬇ SVG" en el header del wheel. Ahora la tecla [S] sobre el wheel (con focus) emite el mismo evento ExportSvgRequested. La línea de hotkeys del footer pasa a: "[D]ial [H]ouses as[X]pects [P]lanets [T]ransits [S]vg [R]eset". ## #8 — Tooltips sobre líneas de aspecto - engine: LineSeg gana fields opcionales `from_body: String`, `to_body: String`, `orb_deg: f32` (default empty/0.0 para back-compat serde). Bridge popula los 6 sites de LineSeg construction (natal aspects + 5 cross overlays) vía un script Python regex que añadió `from_body: body_symbol(a.X).into()` + `to_body: body_symbol(a.Y).into()` + `orb_deg: a.orb_abs_deg() as f32` a cada constructor. - canvas: HoverInfo gana variante Aspect { module_id, from_body, to_body, kind, orb_deg, local_x, local_y }. on_hover_check itera Aspects layers después de los Bodies (precedencia a planetas) y computa distancia punto-segmento con `dist_point_segment` helper — threshold 4px para hover. Tooltip muestra "☉ △ ♂ · orb 2.3°" con prefix de módulo si no es natal. cargo check verde, 8 tests engine + 1 modules verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
a539fab15c |
feat(tahuantinsuyu): fase 24 — observabilidad del broker brahman
Primera pieza concreta de integración con el fractal brahman. La app
deja de ser standalone visible: ahora muestra el estado del broker
en el header con un badge actualizado cada 30s.
- Shell gana enum BrahmanStatus { Pending, Connected { count },
Offline { reason } } + field brahman_status.
- spawn_brahman_status_loop arma un task cx.spawn que cada 30s
invoca brahman_sidecar::list_sessions_blocking sobre el
background_executor (no UI thread — list_sessions_blocking abre su
propio tokio runtime, hacerlo en el UI panicearía con "nested
runtime"). Update via this.update + cx.notify dispara repintado del
badge.
- header agrega pill "Brahman ✓ N sessions" (color accent cuando
conectado), "Brahman · offline" (fg_disabled) o "Brahman · …"
(fg_muted) según el último ping. Entre el separador flex_grow y
el theme_switcher.
- apps Cargo agrega brahman-sidecar como dep directa.
La Card de tahuantinsuyu (fase 1) sigue declarando los flows
`chart-request` (input) y `chart-result` (output), pero ESTOS NO
ESTÁN CABLEADOS A UN DATA PLANE — solo aparecen en el broker como
declaración. Para que tahuantinsuyu PUBLIQUE/CONSUMA datos reales
(otra app del fractal recibiendo una carta serializada, o pidiendo
un cómputo) hay que:
1) Abrir un service_socket Unix server en el sidecar
2) Implementar protocolo postcard sobre ese socket
3) Otro módulo descubre el socket via broker → conecta y envía/recibe
Eso es una fase separada (25+). Esta fase 24 cubre la observabilidad
mínima: la app sabe que el fractal está vivo y muestra el head count.
Cubre el espíritu del brief inicial ("integrar con yahweh que maneja
gpui para intercomunicar widgets") al nivel de visibility — el data
plane real es un proyecto en sí mismo.
cargo check verde. Sin tests nuevos (la lógica nueva es interacción
UI + background task — los tests serían smoke tests del Shell que
no tenemos).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
295c9ba554 |
feat(tahuantinsuyu): fase 22 — layout splitter + atlas loadable desde XDG
Dos features de producción que mejoran la usabilidad sustancialmente. ## #7 — Layout reorganizable con SplitContainer Los 3 paneles ya no tienen tamaños hardcodeados. Reusamos yahweh-widget-splitter (mismo que usa yahweh-shell para sus layouts JSON-config) con 2 niveles: - outer (Vertical): main_split arriba (flex 4) + panel abajo (flex 1) - main_split (Horizontal): tree (flex 1) + canvas (flex 4) El usuario puede arrastrar los dos divisores para redimensionar libremente. Por ejemplo: en una pantalla ancha, dar más al canvas; en una sesión de lectura analítica, agrandar el panel abajo para ver más módulos expandidos. - Shell gana fields main_split + outer_split: Entity<SplitContainer>. - new() construye ambos con ChildSlots envolviendo tree/canvas/panel como AnyView (mismo patrón que LayoutHost de yahweh-shell). - render() simplificado: header + body(outer_split). Las constants TREE_WIDTH y PANEL_HEIGHT desaparecen. - Cargo añade deps: yahweh-core (NodeId, LayoutDirection), yahweh-widget-splitter, yahweh-widget-container-core (ChildSlot). ## #15 — Atlas de ciudades cargable desde TSV El array `CITY_PRESETS` const de 90 ciudades hardcoded ahora es la función `default_city_presets() -> Vec<CityPreset>`. CityPreset.name pasa de `&'static str` a `String` para que el atlas sea construible en runtime. TahuantinsuyuTree gana `city_atlas: Vec<CityPreset>` + setter `set_city_atlas(atlas, cx)`. Al boot, Shell intenta cargar `$XDG_DATA_HOME/tahuantinsuyu/atlas.tsv` y, si existe + parsea bien, reemplaza el atlas hardcoded. Formato TSV (líneas): name<TAB>lat<TAB>lon<TAB>tz_offset_minutes Líneas vacías y `#` comentario se ignoran. Líneas con cualquier parse fallido se descartan en silencio. API pública: `parse_city_atlas_tsv(&str) -> Vec<CityPreset>` (en tahuantinsuyu-tree), reusable por tests/scripts. El usuario que quiera 50.000 ciudades de GeoNames cities5000.txt: 1. wget cities5000.zip de geonames.org 2. awk para extraer (name, lat, lon, tz_offset) y escribir TSV 3. mover a $XDG_DATA_HOME/tahuantinsuyu/atlas.tsv 4. relanzar la app Sin fricción adicional para el usuario común (los 90 hardcoded cubren 99% de casos típicos en español/inglés). cargo check verde, 8 tests engine + 1 test modules verdes. 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>
|
||
|
|
cabdb2927e |
feat(tahuantinsuyu): fase 16 — polish místico (gradient sutil + glow en luminarias)
Dos toques pequeños que dan profundidad visual sin saturar: 1. **Gradient diagonal en el fondo del wheel**: linear_gradient 155° desde palette.dial_ring @ alpha 0.06 hasta palette.angle_highlight @ alpha 0.03. La opacidad mínima asegura que no compite con la geometría pintada encima; el efecto se ve sobre todo en las esquinas del cuadrado (afuera del círculo) y en los gaps entre anillos cuando no hay overlays. Da "shimmer mineral" muy velado. El wheel además ahora tiene rounded(12px) — perfila el cuadrado sin que se sienta como un container. 2. **Glow halo en luminarias natales**: paint_glow nuevo helper que pinta 3 fill_circle concéntricos con (radius × 5/3/1.8, alpha 0.05/0.12/0.22). Aplicado solo a Sol y Luna del layer natal — son los puntos psicológicamente cargados y los que el ojo busca primero. El resto de planetas mantiene su dot limpio. GPUI 0.2 no tiene radial_gradient nativo así que el shading concéntrico discreto cubre el rol. - canvas: imports linear_color_stop + linear_gradient. paint_glow() helper. Loop de Bodies en paint_wheel detecta is_natal && (sun|moon) → paint_glow antes del fill_circle del dot. Convive con todo lo anterior: si la luminaria está oculta (toggle [P] off), el glow también; si hay overlays con sus propios planetas, solo las luminarias natales lucen halo (diferenciable de su contraparte en transit/synastry/return). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
1232e39397 |
feat(tahuantinsuyu): fase 15 — badges de overlays activos en el footer
Cuando hay overlays activos, debajo del info_row aparecen pills con
los nombres de cada uno (Natal, Tránsito ahora, Progresión 38.2a,
Sinastría · Ana, Saturn return 29a) — el usuario ve de un vistazo qué
está mirando sin tener que mapear los anillos manualmente.
El border de cada pill toma color según a qué slot del wheel
pertenece: outer ring (transit/synastry/planetary_return) →
palette.angle_highlight (dorado), inner overlays (progression/
solar_arc) → palette.house_cusp (tono apagado), natal → neutro.
Permite leer la pila de izquierda a derecha y ubicar visualmente cada
glyph del wheel.
- engine: nuevo OverlayMeta { module_id, label } + campo overlays:
Vec<OverlayMeta> en RenderModel. build_render_model lo inicializa
vacío; bridge::compose pushea un OverlayMeta por cada
PipelineRequest después de su build_*_overlay correspondiente. Helper
push_overlay_meta(render, id, label). Labels: "Tránsito ahora",
"Progresión {age:.1}a", "Solar Arc {age:.1}a", "Sinastría · {name}"
(lee partner_chart.label antes de mover el Box al builder),
"{Body} return {age:.0}a" (usa eternal_sky body.name()).
- canvas: render_wheel separa el viejo footer en info_row (Asc/MC/ms +
offset + hotkeys) y un badges_row opcional. badges_row aparece solo
cuando render.overlays != empty. Pill helper centralizado: bg
panel_alt, border 1px, text size 10, rounded 10. Border color
decidido por module_id para correlacionar con el ring visual.
Compatible con compute_mock (que setea overlays = vec![] — ningún
mock badge). Persiste sin cambios — los configs siguen guardando su
estado, los OverlayMeta se reconstruyen en cada compose desde los
requests activos.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|