feat(0035d): doble click en Group abre tableview filtrado por group_id

- entity_ops: EntityRowSnapshot.group_id + SQL con COALESCE(group_id,'')
  + deteccion via PRAGMA para BDs viejas sin la columna.
- views.h: TableRow.group_id + AppState.table_filter_group_id /
  table_filter_group_name (RAM-only).
- main.cpp: dispatch en want_open_note — si type_ref == "Group", setea
  filtro de grupo + abre panel Table en vez de Note. Reset de search
  buf y col_filters al entrar al drill-in para que el usuario vea todo
  el contenido del grupo.
- views.cpp: build_visible compone group_id con search/tabs/col_filters
  (AND). types_present se reduce a tipos presentes en el grupo cuando
  hay drill-in activo. Header pintado en amarillo con TI_FOLDER +
  contador + boton "Clear group filter". Al cerrarse el panel se
  limpia el filtro automaticamente.

Tests: pytest 35 passed (WSL) / 24 passed + 11 skipped (Windows).

Refs: issues/0035d-tableview-drill-in.md
This commit is contained in:
2026-05-03 14:57:20 +02:00
parent eff273a2d4
commit b67da92e18
5 changed files with 99 additions and 8 deletions
+26 -5
View File
@@ -729,6 +729,7 @@ static bool load_input(bool first_load) {
tr.type_ref = std::move(s.type_ref);
tr.status = std::move(s.status);
tr.updated_at = std::move(s.updated_at);
tr.group_id = std::move(s.group_id);
g_app.table_rows.push_back(std::move(tr));
}
ge::views_table_refresh_indices(g_app);
@@ -1558,6 +1559,7 @@ static void render() {
tr.type_ref = std::move(s.type_ref);
tr.status = std::move(s.status);
tr.updated_at = std::move(s.updated_at);
tr.group_id = std::move(s.group_id);
g_app.table_rows.push_back(std::move(tr));
}
ge::views_table_refresh_indices(g_app);
@@ -1855,21 +1857,40 @@ static void render() {
}
// Note editor — abrir / guardar.
// Excepcion (issue 0035d): si el nodo es un Group, en lugar de abrir
// Note se abre el panel Table con filtro por group_id.
if (g_app.want_open_note && g_app.open_note_target >= 0
&& g_app.open_note_target < g_graph.node_count) {
int n = g_app.open_note_target;
const char* sql_id = ge::entity_index_lookup(g_idx, g_graph.nodes[n].user_data);
if (sql_id) {
// Detectar si el nodo es de tipo Group.
bool is_group = false;
uint16_t tid = g_graph.nodes[n].type_id;
const char* type_name = (tid < (uint16_t)g_graph.type_count
&& g_graph.types[tid].name)
? g_graph.types[tid].name : "";
if (type_name && std::strcmp(type_name, "Group") == 0) is_group = true;
if (is_group && sql_id) {
// Drill-in: abrir tableview filtrada por group_id = sql_id.
g_app.table_filter_group_id = sql_id;
const char* lbl = graph::graph_label(&g_graph, g_graph.nodes[n].label_idx);
g_app.table_filter_group_name = lbl ? lbl : sql_id;
// Reset filtros que pueden ocultar las filas del grupo.
g_app.table_search_buf[0] = 0;
g_app.table_col_filters.clear();
g_app.table_show_all = true;
g_app.panel_table = true;
ImGui::SetWindowFocus("Table");
} else if (sql_id) {
std::string md;
ge::entity_get_notes(g_app.input_db_path.c_str(), sql_id, &md);
g_app.note_node = n;
g_app.note_entity_id = sql_id;
const char* lbl = graph::graph_label(&g_graph, g_graph.nodes[n].label_idx);
g_app.note_entity_label = lbl ? lbl : "";
uint16_t tid = g_graph.nodes[n].type_id;
g_app.note_entity_type = (tid < (uint16_t)g_graph.type_count
&& g_graph.types[tid].name)
? g_graph.types[tid].name : "";
g_app.note_entity_type = type_name;
// Asegura buffer >= max(64KB, contenido + holgura).
size_t need = md.size() + 4096;
if (need < 65536) need = 65536;