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>
This commit is contained in:
sergio
2026-05-18 00:05:50 +00:00
parent a539fab15c
commit 6191ce7dee
4 changed files with 313 additions and 7 deletions
@@ -166,7 +166,7 @@ pub enum Geometry {
Points(Vec<PointMark>),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct LineSeg {
/// Grados zodiacales del extremo "a".
pub from_deg: f32,
@@ -176,6 +176,17 @@ pub struct LineSeg {
/// resuelve a color.
pub kind: String,
pub opacity: f32,
/// Cuerpo en el extremo "a" — populado para LineSegs de aspectos
/// (natal × natal, cross con overlays). Vacío en `Default::default`
/// para serde back-compat.
#[serde(default)]
pub from_body: String,
/// Cuerpo en el extremo "b".
#[serde(default)]
pub to_body: String,
/// Orb absoluto en grados (para tooltips).
#[serde(default)]
pub orb_deg: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]