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:
@@ -602,9 +602,12 @@ static bool load_input(bool first_load) {
|
||||
std::fprintf(stderr, "[graph_explorer] load failed: %s\n", stats.error_msg);
|
||||
return false;
|
||||
}
|
||||
// Filtro de grupos colapsados (issue 0035b). Se aplica tras la carga
|
||||
// bruta — el loader sigue siendo agnostico al concepto de grupo.
|
||||
ge::apply_group_filter(&g_graph, g_input.uri, g_app.group_expanded);
|
||||
std::fprintf(stdout,
|
||||
"[graph_explorer] loaded %d nodes, %d edges, %d types, %d rel_types from %s\n",
|
||||
stats.nodes_loaded, stats.edges_loaded,
|
||||
g_graph.node_count, g_graph.edge_count,
|
||||
stats.types_discovered, stats.rel_types_discovered, g_input.uri);
|
||||
|
||||
// types.yaml
|
||||
@@ -1360,7 +1363,7 @@ static void render() {
|
||||
}
|
||||
|
||||
graph::GraphLoadStats stats{};
|
||||
if (ge::reload_graph(g_input, &g_graph, &stats)) {
|
||||
if (ge::reload_graph(g_input, &g_graph, &stats, &g_app.group_expanded)) {
|
||||
ge::views_reset_visibility(g_app);
|
||||
ge::views_apply_visibility(g_app);
|
||||
|
||||
@@ -1519,7 +1522,7 @@ static void render() {
|
||||
ge::layout_store_save(g_graph_hash, g_graph);
|
||||
}
|
||||
graph::GraphLoadStats stats{};
|
||||
if (!ge::reload_graph(g_input, &g_graph, &stats)) return;
|
||||
if (!ge::reload_graph(g_input, &g_graph, &stats, &g_app.group_expanded)) return;
|
||||
ge::entity_index_build(g_input.uri, &g_idx);
|
||||
ge::views_reset_visibility(g_app);
|
||||
ge::views_apply_visibility(g_app);
|
||||
@@ -1800,7 +1803,7 @@ static void render() {
|
||||
// Reload del grafo para que cambios de name/type/etc. se reflejen
|
||||
// en el viewport (label, color del tipo, etc.).
|
||||
graph::GraphLoadStats stats{};
|
||||
if (ge::reload_graph(g_input, &g_graph, &stats)) {
|
||||
if (ge::reload_graph(g_input, &g_graph, &stats, &g_app.group_expanded)) {
|
||||
ge::entity_index_build(g_input.uri, &g_idx);
|
||||
ge::views_reset_visibility(g_app);
|
||||
ge::views_apply_visibility(g_app);
|
||||
|
||||
Reference in New Issue
Block a user