feat(0035b): renderer oculta hijos de grupos colapsados + dedup aristas

- AppState anade `group_expanded` (unordered_map<string,bool>) en RAM,
  default vacio = todos los grupos colapsados al arranque. Sin
  persistencia entre sesiones (fase 1).
- `apply_group_filter(GraphData*, db_path, expanded)` consulta
  entities (id, group_id, type_ref) de operations.db, marca como
  ocultos los nodos cuyo group_id apunta a un grupo no expandido,
  compacta `g->nodes` y re-mapea indices de aristas.
- Aristas:
  * Cross-edge (un extremo oculto, otro fuera): se redirige el
    extremo oculto al nodo del grupo. Sin dedup (issue 0035 dec. 5).
  * Internas (ambos extremos en el mismo grupo colapsado): se ocultan.
  * Inter-grupo (ambos en grupos colapsados distintos): dedup por
    par no ordenado (group_a, group_b) + rel_type, una linea por par.
  * Orfanas (group_id apunta a un grupo no presente en grafo): el
    nodo se oculta y sus aristas se descartan.
- Centralizado: el filtro corre en `reload_graph()` cuando se le
  pasa `group_expanded`, y en `load_input()` tras el load inicial.
  Cubre las 4 rutas de carga del app (toolbar reload, mutaciones,
  inspector save, primera carga / switch project).
- Idempotente sobre un grafo ya filtrado y robusto frente a BDs sin
  columna `group_id` (schema antiguo) — no toca el grafo.

Smoke test manual con 3 BDs sintéticas:
- Grupo + 2 children + edges cruzadas/internas: nodes 5→3, edges
  4→3 (internal hidden, cross redirected).
- 2 grupos con 4 cross-edges entre ellos: edges 4→1 (dedup).
- group_id huerfano: nodo oculto + arista descartada.

Build clean en Windows. Tests verdes:
- WSL pytest: 32 passed.
- Windows pytest: 21 passed + 11 skipped.

Refs: issues/0035b-renderer-hides-grouped-children.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 14:48:17 +02:00
parent fbe408015d
commit d3da1416f3
5 changed files with 302 additions and 8 deletions
+9
View File
@@ -84,6 +84,15 @@ struct AppState {
// main.cpp lo escribe tras cargar y los handlers lo leen.
std::string input_db_path;
// ---- Grouping (issue 0035b) ---------------------------------------------
// Estado de expansion de nodos `Group` en RAM. Default vacio = todos los
// grupos colapsados. No persiste entre sesiones (fase 1). El filtro del
// loader (apply_group_filter) consulta este map: si una entidad tiene
// `group_id != NULL` y el grupo padre no esta en este map con valor true,
// la entidad se oculta del grafo. Sin UI todavia para togglear; se setean
// valores manualmente desde tests/debug.
std::unordered_map<std::string, bool> group_expanded;
// Add-node toolbar input.
char add_buf[256] = {};