diff --git a/CMakeLists.txt b/CMakeLists.txt index d509b3b..2205c01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ add_imgui_app(graph_explorer layout_store.cpp entity_ops.cpp project_manager.cpp - tableview.cpp + node_groups.cpp jobs.cpp enrichers.cpp chat.cpp diff --git a/gx-cli b/gx-cli index e1536ff..a1e43f2 100755 --- a/gx-cli +++ b/gx-cli @@ -429,7 +429,7 @@ def cmd_table_list(args) -> None: def cmd_table_promote(args) -> None: - """Promociona una fila DuckDB a entidad. Replica tableview_promote_row.""" + """Promociona una fila DuckDB a entidad. Replica node_groups_promote_row.""" cn = _connect(_ops_db()) try: row = cn.execute( diff --git a/issues/0036a-rename-nodegroups.md b/issues/0036a-rename-nodegroups.md new file mode 100644 index 0000000..a729ae0 --- /dev/null +++ b/issues/0036a-rename-nodegroups.md @@ -0,0 +1,93 @@ +--- +id: 0036a +title: Rename Table-expanded -> NodeGroups (paperwork, sin cambio funcional) +status: done +priority: high +created: 2026-05-04 +parent: 0036 +--- + +## Objetivo + +Cambio puramente de nombres. Cero cambio de comportamiento. Bloquea +los issues siguientes (0036b-f) que ya asumen la nueva convencion. + +## Cambios + +### Archivos renombrados + +- `tableview.cpp` -> `node_groups.cpp` +- `tableview.h` -> `node_groups.h` + +### Tipos (en el sub-repo del app) + +| Antes | Despues | +|---|---| +| `TableWindowState` (en `views.h`) | `NodeGroupsWindowState` | +| `TableMetadata` (en `tableview.h`) | `NodeGroupsMeta` | +| `TablePageRow` (en `tableview.h`) | `NodeGroupsRow` | + +### Campos AppState + +| Antes | Despues | +|---|---| +| `app.table_windows` | `app.node_groups_windows` | +| `app.table_node_counts` | `app.node_groups_counts` | +| `app.toggle_expanded_id` | `app.toggle_nodegroups_id` | +| `app.want_toggle_expanded` | `app.want_toggle_nodegroups` | +| `app.want_promote_row` (relacionado) | dejar igual por ahora — es promote de DuckDB, lo retocaremos en 0036d | + +### Funciones y prefijos + +| Antes | Despues | +|---|---| +| `tableview_load_metadata` | `node_groups_load_metadata` | +| `tableview_page_rows` | `node_groups_page_rows` | +| `tableview_refresh_counts` | `node_groups_refresh_counts` | +| Cualquier `tableview_*` no relacionado al panel generico `Table` | `node_groups_*` | + +**OJO**: el panel generico `Table` (`views_table` en `views.cpp`) NO se +renombra. Es la lista global de entidades, no la window por contenedor. +Lo afectado es solo lo que abre el "expand" de un nodo Table-typed +(hoy) y lo que abrira el "drill" de un Group (mañana, 0036c). + +### CMakeLists.txt + +Actualizar `apps/graph_explorer/CMakeLists.txt` para usar el nombre +nuevo `node_groups.cpp` en lugar de `tableview.cpp`. + +### Strings de UI + +| Antes | Despues | +|---|---| +| Window title `"Table: "` | `"NodeGroups: "` | +| `"Expand table"` (context menu) | `"Open NodeGroups"` | +| `"Collapse table"` | `"Close NodeGroups"` | +| Mensajes de logs `[tableview]` | `[node_groups]` | + +### Comentarios y docs + +Buscar y reemplazar referencias a "Table-expanded", "Table expanded", +"tabla expandida" en comentarios. NO tocar los issues completados que +documentan la historia (0010, 0011) — son archivo historico. + +## Acceptance criteria + +- Build C++ verde sin warnings nuevos. +- pytest verde en WSL y Windows. +- Smoke test manual: abrir un Table existente con right-click "Open + NodeGroups" → la window se abre como antes, mismo contenido, + mismo titulo "NodeGroups: ". +- `git grep -i "tableview\|TableWindow\|TablePageRow\|TableMetadata"` + solo encuentra ocurrencias en `issues/completed/` (historia) y en + el panel generico `Table` (que NO se toca). + +## TBD + +Branch `issue/0036a-rename-nodegroups`, merge `--no-ff` a master. + +## Out of scope + +- Cualquier cambio funcional (eso es 0036b y siguientes). +- Renombrar el panel generico `Table` (queda como esta). +- Tocar la lista de issues completados. diff --git a/main.cpp b/main.cpp index c63dfe9..2a7f526 100644 --- a/main.cpp +++ b/main.cpp @@ -33,7 +33,7 @@ #include "../../../../cpp/vendor/sqlite3/sqlite3.h" -#include "tableview.h" +#include "node_groups.h" #include "duckdb.h" #include @@ -707,15 +707,15 @@ static bool load_input(bool first_load) { // Cache de conteos de Table nodes (issue 0010). if (g_input.uri) { - ge::tableview_refresh_counts(g_input.uri, &g_app.table_node_counts); + ge::node_groups_refresh_counts(g_input.uri, &g_app.node_groups_counts); int64_t total_rows = 0; - for (auto& kv : g_app.table_node_counts) total_rows += kv.second; + for (auto& kv : g_app.node_groups_counts) total_rows += kv.second; std::fprintf(stdout, "[graph_explorer] table counts refreshed: %zu tables, %lld total rows\n", - g_app.table_node_counts.size(), (long long)total_rows); + g_app.node_groups_counts.size(), (long long)total_rows); // Sync de windows expandidas (issue 0011) — reabre las que el // usuario tenia abiertas en la sesion previa (metadata.expanded=true). - ge::views_table_windows_sync(g_app, g_input.uri); + ge::views_node_groups_windows_sync(g_app, g_input.uri); } // Cache de la vista tabla (issue 0004) — pull bulk + neighbors desde grafo. @@ -882,15 +882,15 @@ static void render_context_menu() { const char* sql_id = ge::entity_index_lookup(g_idx, n.user_data); if (is_table && sql_id) { - // Determinar estado expanded actual sin ir a BD: mira table_windows. - bool currently_expanded = - g_app.table_windows.find(sql_id) != g_app.table_windows.end(); - const char* lbl_exp = currently_expanded - ? TI_X " Collapse table" - : TI_TABLE " Expand table"; + // Determinar estado actual sin ir a BD: mira node_groups_windows. + bool currently_open = + g_app.node_groups_windows.find(sql_id) != g_app.node_groups_windows.end(); + const char* lbl_exp = currently_open + ? TI_X " Close NodeGroups" + : TI_TABLE " Open NodeGroups"; if (ImGui::MenuItem(lbl_exp)) { - g_app.want_toggle_expanded = true; - g_app.toggle_expanded_id = sql_id; + g_app.want_toggle_nodegroups = true; + g_app.toggle_nodegroups_id = sql_id; } ImGui::Separator(); } @@ -1580,7 +1580,7 @@ static void render() { // Reaplica types.yaml + atlas. Sin esto, despues de cualquier // mutacion los tipos pierden color/shape/icon (todo nodo vuelve a - // circulo gris). Issue: al promover desde tableview el Table + // circulo gris). Issue: al promover desde node_groups el Table // dejaba de ser cuadrado. if (!g_app.parsed_types.entities.empty() || !g_app.parsed_types.relations.empty()) { @@ -1592,10 +1592,10 @@ static void render() { } // Refresh Table node counts (issue 0010). - ge::tableview_refresh_counts(g_input.uri, &g_app.table_node_counts); + ge::node_groups_refresh_counts(g_input.uri, &g_app.node_groups_counts); // Sincroniza windows (issue 0011) por si una Table aparecio o desaparecio. - ge::views_table_windows_sync(g_app, g_input.uri); + ge::views_node_groups_windows_sync(g_app, g_input.uri); // Refresh table cache (issue 0004). std::vector snap; @@ -1688,30 +1688,30 @@ static void render() { } // ---- Table node UI fase 2 (issue 0011) ---- - if (g_app.want_toggle_expanded && !g_app.toggle_expanded_id.empty() + if (g_app.want_toggle_nodegroups && !g_app.toggle_nodegroups_id.empty() && !g_input_path.empty()) { - std::string id = g_app.toggle_expanded_id; - bool currently = g_app.table_windows.find(id) != g_app.table_windows.end(); - ge::tableview_set_expanded(g_input_path.c_str(), id.c_str(), !currently); - ge::views_table_windows_sync(g_app, g_input_path.c_str()); - g_app.want_toggle_expanded = false; - g_app.toggle_expanded_id.clear(); + std::string id = g_app.toggle_nodegroups_id; + bool currently = g_app.node_groups_windows.find(id) != g_app.node_groups_windows.end(); + ge::node_groups_set_expanded(g_input_path.c_str(), id.c_str(), !currently); + ge::views_node_groups_windows_sync(g_app, g_input_path.c_str()); + g_app.want_toggle_nodegroups = false; + g_app.toggle_nodegroups_id.clear(); } // Cierre via X de la ventana -> bajar expanded en BD. - for (auto it = g_app.table_windows.begin(); it != g_app.table_windows.end(); ) { + for (auto it = g_app.node_groups_windows.begin(); it != g_app.node_groups_windows.end(); ) { if (!it->second.open && !g_input_path.empty()) { - ge::tableview_set_expanded(g_input_path.c_str(), + ge::node_groups_set_expanded(g_input_path.c_str(), it->first.c_str(), false); - it = g_app.table_windows.erase(it); + it = g_app.node_groups_windows.erase(it); } else ++it; } // Refrescar la pagina si alguna window esta dirty. - for (auto& kv : g_app.table_windows) { + for (auto& kv : g_app.node_groups_windows) { auto& w = kv.second; if (!w.page_dirty) continue; const auto& m = w.meta; w.last_error.clear(); - bool ok_count = ge::tableview_count(m.duckdb_path_abs.c_str(), + bool ok_count = ge::node_groups_count(m.duckdb_path_abs.c_str(), m.table_name.c_str(), m.filter_sql.empty() ? nullptr : m.filter_sql.c_str(), &w.total_rows); @@ -1725,14 +1725,14 @@ static void render() { } if (m.columns.empty()) { std::vector cols; - if (ge::tableview_list_columns(m.duckdb_path_abs.c_str(), + if (ge::node_groups_list_columns(m.duckdb_path_abs.c_str(), m.table_name.c_str(), &cols)) { - ge::tableview_set_columns(g_input_path.c_str(), + ge::node_groups_set_columns(g_input_path.c_str(), m.entity_id.c_str(), cols); w.meta.columns = cols; } } - bool ok_page = ge::tableview_page(m.duckdb_path_abs.c_str(), + bool ok_page = ge::node_groups_page(m.duckdb_path_abs.c_str(), m.table_name.c_str(), m.id_column.c_str(), w.meta.columns, m.filter_sql.empty() ? nullptr : m.filter_sql.c_str(), @@ -1749,11 +1749,11 @@ static void render() { } if (g_app.want_promote_row && !g_app.promote_table_id.empty() && !g_input_path.empty()) { - ge::TableMetadata m; - if (ge::tableview_get_metadata(g_input_path.c_str(), + ge::NodeGroupsMeta m; + if (ge::node_groups_get_metadata(g_input_path.c_str(), g_app.promote_table_id.c_str(), &m)) { char new_id[128] = {}; - if (ge::tableview_promote_row(g_input_path.c_str(), + if (ge::node_groups_promote_row(g_input_path.c_str(), g_app.promote_table_id.c_str(), m.duckdb_path_abs.c_str(), m.table_name.c_str(), @@ -1763,8 +1763,8 @@ static void render() { new_id, sizeof(new_id))) { std::fprintf(stdout, "[promote] %s -> %s\n", g_app.promote_row_id.c_str(), new_id); - auto it = g_app.table_windows.find(g_app.promote_table_id); - if (it != g_app.table_windows.end()) it->second.page_dirty = true; + auto it = g_app.node_groups_windows.find(g_app.promote_table_id); + if (it != g_app.node_groups_windows.end()) it->second.page_dirty = true; reload_after_mutation(); g_app.want_focus_entity = true; g_app.focus_entity_id = new_id; @@ -1776,10 +1776,10 @@ static void render() { } if (g_app.want_demote_entity && !g_app.demote_entity_id.empty() && !g_input_path.empty()) { - if (ge::tableview_demote_row(g_input_path.c_str(), + if (ge::node_groups_demote_row(g_input_path.c_str(), g_app.demote_entity_id.c_str())) { std::fprintf(stdout, "[demote] %s\n", g_app.demote_entity_id.c_str()); - for (auto& kv : g_app.table_windows) kv.second.page_dirty = true; + for (auto& kv : g_app.node_groups_windows) kv.second.page_dirty = true; reload_after_mutation(); } g_app.want_demote_entity = false; @@ -1806,17 +1806,17 @@ static void render() { if (g_app.want_import) { g_app.want_import = false; g_app.import_error.clear(); - std::string duck_abs = ge::tableview_resolve_path( + std::string duck_abs = ge::node_groups_resolve_path( g_input_path.c_str(), g_app.import_duckdb_buf); std::string err; - if (!ge::tableview_ingest_file(duck_abs.c_str(), + if (!ge::node_groups_ingest_file(duck_abs.c_str(), g_app.import_path_buf, g_app.import_table_buf, ge::INGEST_AUTO, &err)) { g_app.import_error = "Ingest failed: " + err; } else { char new_id[80] = {}; - if (ge::tableview_create(g_input_path.c_str(), + if (ge::node_groups_create(g_input_path.c_str(), g_app.import_table_buf, g_app.import_duckdb_buf, g_app.import_table_buf, @@ -1923,7 +1923,7 @@ static void render() { 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. + // Drill-in: abrir Table panel filtrado 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; @@ -2007,7 +2007,7 @@ static void render() { &get_label_cb, nullptr); } // Table node overlay (issue 0010) — encima de las labels. - ge::views_table_overlay(g_app); + ge::views_node_groups_overlay(g_app); } ImGui::End(); } else { @@ -2051,7 +2051,7 @@ static void render() { ge::views_table(g_app); // Table node windows (issue 0011) — una por Table expandida. - ge::views_table_window(g_app); + ge::views_node_groups_window(g_app); ge::views_import_dataset_modal(g_app); // Jobs panel (issue 0026) — flotante, dockeable. @@ -2181,7 +2181,7 @@ int main(int argc, char** argv) { return test_types_yaml_roundtrip(argv[++i]); } else if (std::strcmp(a, "--test-duckdb") == 0 && i + 1 < argc) { const char* p = argv[++i]; - if (!ge::tableview_smoke_test(p)) { + if (!ge::node_groups_smoke_test(p)) { std::fprintf(stderr, "[duckdb] smoke test FAILED for %s\n", p); return 2; } @@ -2209,25 +2209,25 @@ int main(int argc, char** argv) { duckdb_disconnect(&cn); duckdb_close(&db); int64_t total = 0; - if (!ge::tableview_count(p, "people", nullptr, &total) || total != 1000000) { - std::fprintf(stderr, "[tableview_count] expected 1000000, got %lld\n", + if (!ge::node_groups_count(p, "people", nullptr, &total) || total != 1000000) { + std::fprintf(stderr, "[node_groups_count] expected 1000000, got %lld\n", (long long)total); return 2; } std::vector cols = { "name", "age" }; - std::vector page; - if (!ge::tableview_page(p, "people", "id", cols, nullptr, + std::vector page; + if (!ge::node_groups_page(p, "people", "id", cols, nullptr, nullptr, nullptr, 500000, 10, &page)) { - std::fprintf(stderr, "[tableview_page] failed\n"); + std::fprintf(stderr, "[node_groups_page] failed\n"); return 2; } if (page.size() != 10) { - std::fprintf(stderr, "[tableview_page] expected 10 rows, got %zu\n", + std::fprintf(stderr, "[node_groups_page] expected 10 rows, got %zu\n", page.size()); return 2; } std::fprintf(stdout, - "[tableview] OK — count=%lld, page[0]={id=%s, name=%s, age=%s}\n", + "[node_groups] OK — count=%lld, page[0]={id=%s, name=%s, age=%s}\n", (long long)total, page[0].id.c_str(), page[0].values.size() > 0 ? page[0].values[0].c_str() : "", page[0].values.size() > 1 ? page[0].values[1].c_str() : ""); diff --git a/tableview.cpp b/node_groups.cpp similarity index 95% rename from tableview.cpp rename to node_groups.cpp index 3fbcf88..79baa5b 100644 --- a/tableview.cpp +++ b/node_groups.cpp @@ -1,4 +1,4 @@ -#include "tableview.h" +#include "node_groups.h" #include "duckdb.h" #include "../../../../cpp/vendor/sqlite3/sqlite3.h" @@ -62,7 +62,7 @@ bool duck_query_silent(duckdb_connection cn, const char* sql) { } // namespace -bool tableview_smoke_test(const char* duckdb_path) { +bool node_groups_smoke_test(const char* duckdb_path) { DuckHandle h; if (!h.open(duckdb_path)) return false; duckdb_result r; @@ -76,7 +76,7 @@ bool tableview_smoke_test(const char* duckdb_path) { return ok; } -bool tableview_count(const char* duckdb_path, +bool node_groups_count(const char* duckdb_path, const char* duck_table, const char* sql_filter, int64_t* out) @@ -94,7 +94,7 @@ bool tableview_count(const char* duckdb_path, } duckdb_result r; if (duckdb_query(h.cn, sql.c_str(), &r) == DuckDBError) { - std::fprintf(stderr, "[tableview_count] %s\n", + std::fprintf(stderr, "[node_groups_count] %s\n", duckdb_result_error(&r) ? duckdb_result_error(&r) : "?"); duckdb_destroy_result(&r); return false; @@ -106,7 +106,7 @@ bool tableview_count(const char* duckdb_path, return true; } -bool tableview_page(const char* duckdb_path, +bool node_groups_page(const char* duckdb_path, const char* duck_table, const char* id_column, const std::vector& columns, @@ -114,7 +114,7 @@ bool tableview_page(const char* duckdb_path, const char* ops_db, const char* row_type, int64_t offset, int64_t limit, - std::vector* out) + std::vector* out) { if (!out) return false; out->clear(); @@ -177,7 +177,7 @@ bool tableview_page(const char* duckdb_path, duckdb_result r; if (duckdb_query(h.cn, sel.c_str(), &r) == DuckDBError) { const char* e = duckdb_result_error(&r); - std::fprintf(stderr, "[tableview_page] FAIL: %s\n SQL: %s\n", + std::fprintf(stderr, "[node_groups_page] FAIL: %s\n SQL: %s\n", e ? e : "?", sel.c_str()); duckdb_destroy_result(&r); return false; @@ -186,7 +186,7 @@ bool tableview_page(const char* duckdb_path, idx_t cols = duckdb_column_count(&r); out->reserve((size_t)rows); for (idx_t row = 0; row < rows; ++row) { - TablePageRow tr; + NodeGroupsRow tr; // col 0 = id if (!duckdb_value_is_null(&r, 0, row)) { char* v = duckdb_value_varchar(&r, 0, row); @@ -220,7 +220,7 @@ bool tableview_page(const char* duckdb_path, return true; } -bool tableview_create(const char* ops_db, +bool node_groups_create(const char* ops_db, const char* name, const char* duckdb_path, const char* duck_table, @@ -317,7 +317,7 @@ static std::string normalize_path(std::string p) { return p; } -std::string tableview_resolve_path(const char* ops_db, const char* maybe_rel) { +std::string node_groups_resolve_path(const char* ops_db, const char* maybe_rel) { if (!maybe_rel) return ""; if (is_absolute(maybe_rel)) return normalize_path(maybe_rel); std::string base = dirname_of(ops_db); @@ -325,7 +325,7 @@ std::string tableview_resolve_path(const char* ops_db, const char* maybe_rel) { return normalize_path(base + "/" + maybe_rel); } -bool tableview_refresh_counts(const char* ops_db, +bool node_groups_refresh_counts(const char* ops_db, std::unordered_map* out) { if (!ops_db || !out) return false; @@ -352,13 +352,13 @@ bool tableview_refresh_counts(const char* ops_db, const unsigned char* tab_p = sqlite3_column_text(st, 2); const unsigned char* flt_p = sqlite3_column_text(st, 3); if (!id_p || !path_p || !tab_p) continue; - std::string abs = tableview_resolve_path(ops_db, (const char*)path_p); + std::string abs = node_groups_resolve_path(ops_db, (const char*)path_p); int64_t total = 0; - if (!tableview_count(abs.c_str(), (const char*)tab_p, + if (!node_groups_count(abs.c_str(), (const char*)tab_p, flt_p ? (const char*)flt_p : nullptr, &total)) { std::fprintf(stderr, - "[tableview_refresh_counts] count failed for id=%s\n", id_p); + "[node_groups_refresh_counts] count failed for id=%s\n", id_p); continue; } out->emplace(fnv1a64((const char*)id_p), total); @@ -415,7 +415,7 @@ std::vector parse_json_string_array(const char* s) { } // namespace -bool tableview_list_columns(const char* duckdb_path, +bool node_groups_list_columns(const char* duckdb_path, const char* duck_table, std::vector* out) { @@ -438,11 +438,11 @@ bool tableview_list_columns(const char* duckdb_path, return true; } -bool tableview_get_metadata(const char* ops_db, const char* entity_id, - TableMetadata* out) +bool node_groups_get_metadata(const char* ops_db, const char* entity_id, + NodeGroupsMeta* out) { if (!ops_db || !entity_id || !out) return false; - *out = TableMetadata{}; + *out = NodeGroupsMeta{}; out->entity_id = entity_id; sqlite3* db = nullptr; if (sqlite3_open_v2(ops_db, &db, SQLITE_OPEN_READONLY, nullptr) != SQLITE_OK) { @@ -486,7 +486,7 @@ bool tableview_get_metadata(const char* ops_db, const char* entity_id, if (out->label_column.empty()) out->label_column = "name"; out->columns = parse_json_string_array(cols_json.c_str()); out->expanded = (exp_json == "true" || exp_json == "1"); - out->duckdb_path_abs = tableview_resolve_path(ops_db, out->duckdb_path.c_str()); + out->duckdb_path_abs = node_groups_resolve_path(ops_db, out->duckdb_path.c_str()); ok = true; } sqlite3_finalize(st); @@ -521,7 +521,7 @@ bool exec_metadata_patch(const char* ops_db, const char* entity_id, } // namespace -bool tableview_set_expanded(const char* ops_db, const char* entity_id, +bool node_groups_set_expanded(const char* ops_db, const char* entity_id, bool expanded) { if (!ops_db || !entity_id) return false; @@ -531,7 +531,7 @@ bool tableview_set_expanded(const char* ops_db, const char* entity_id, return exec_metadata_patch(ops_db, entity_id, clause.c_str()); } -bool tableview_set_columns(const char* ops_db, const char* entity_id, +bool node_groups_set_columns(const char* ops_db, const char* entity_id, const std::vector& columns) { if (!ops_db || !entity_id) return false; @@ -586,7 +586,7 @@ bool find_existing_promotion(const char* ops_db, const char* duckdb_path, } // namespace -bool tableview_promote_row(const char* ops_db, +bool node_groups_promote_row(const char* ops_db, const char* table_entity_id, const char* duckdb_path, const char* duck_table, @@ -729,7 +729,7 @@ bool tableview_promote_row(const char* ops_db, } } -bool tableview_demote_row(const char* ops_db, const char* entity_id) { +bool node_groups_demote_row(const char* ops_db, const char* entity_id) { if (!ops_db || !entity_id) return false; sqlite3* db = nullptr; if (sqlite3_open_v2(ops_db, &db, SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK) { @@ -774,7 +774,7 @@ const char* ingest_func_name(IngestKind k) { } // namespace -bool tableview_ingest_file(const char* duckdb_path, +bool node_groups_ingest_file(const char* duckdb_path, const char* file_path, const char* dest_table, IngestKind kind, diff --git a/tableview.h b/node_groups.h similarity index 79% rename from tableview.h rename to node_groups.h index 62dbe86..8a64cdd 100644 --- a/tableview.h +++ b/node_groups.h @@ -4,10 +4,10 @@ #include #include -// Vista tabular respaldada por DuckDB (issue 0010). Cada nodo `Table` del -// grafo apunta via metadata a un archivo `.duckdb` y a una tabla dentro -// de el. Las filas viven en DuckDB; el grafo solo materializa las que -// se "promueven" a entidades (issue 0011). +// NodeGroups — vista tabular respaldada por DuckDB (issue 0010, renombrada +// en 0036a). Cada nodo `Table` del grafo apunta via metadata a un archivo +// `.duckdb` y a una tabla dentro de el. Las filas viven en DuckDB; el grafo +// solo materializa las que se "promueven" a entidades (issue 0011). // // Convencion de paths: `metadata.duckdb_path` es relativo al directorio del // proyecto (la raiz donde vive operations.db). El caller resuelve a path @@ -15,7 +15,7 @@ namespace ge { -struct TablePageRow { +struct NodeGroupsRow { std::string id; // valor del id_column en duckdb (key natural) std::vector values; // un valor por columna en `columns[]` std::string promoted_entity_id; // "" si la fila no esta promovida; sino, ops.entities.id @@ -25,7 +25,7 @@ struct TablePageRow { // type_ref='Table' y metadata apuntando al duckdb_path/table_name. Genera // un id propio. Devuelve false si SQLite falla o si los argumentos basicos // estan vacios. -bool tableview_create(const char* ops_db, +bool node_groups_create(const char* ops_db, const char* name, const char* duckdb_path, const char* duck_table, @@ -35,7 +35,7 @@ bool tableview_create(const char* ops_db, // Cuenta las filas de duckdb_path/duck_table aplicando opcionalmente // `sql_filter` (clausula WHERE sin la palabra WHERE — vacio = sin filtro). // Devuelve false en error de IO/parse. -bool tableview_count(const char* duckdb_path, +bool node_groups_count(const char* duckdb_path, const char* duck_table, const char* sql_filter, int64_t* out); @@ -44,7 +44,7 @@ bool tableview_count(const char* duckdb_path, // valores de `columns` resueltos a string + el flag `promoted_entity_id` // computado via LEFT JOIN contra ops.entities (DuckDB attach a SQLite). // limit clampeado en [1,5000]. -bool tableview_page(const char* duckdb_path, +bool node_groups_page(const char* duckdb_path, const char* duck_table, const char* id_column, const std::vector& columns, @@ -52,20 +52,20 @@ bool tableview_page(const char* duckdb_path, const char* ops_db, // para LEFT JOIN de promovidas const char* row_type, // discriminante en ops.entities int64_t offset, int64_t limit, - std::vector* out); + std::vector* out); // Smoke test: abre el .duckdb, corre `SELECT 42 AS x` y verifica que // devuelve la fila esperada. Devuelve true si todo OK. -bool tableview_smoke_test(const char* duckdb_path); +bool node_groups_smoke_test(const char* duckdb_path); // Resuelve un path posiblemente relativo a la ubicacion de operations.db. // Si es absoluto (empieza por '/' o ':' en Windows), se devuelve // tal cual. -std::string tableview_resolve_path(const char* ops_db, const char* maybe_rel); +std::string node_groups_resolve_path(const char* ops_db, const char* maybe_rel); // Refresca el cache de conteos de filas por nodo Table. Lee // type_ref='Table' de operations.db, extrae metadata.duckdb_path/table_name, -// llama a tableview_count y guarda el resultado indexado por +// llama a node_groups_count y guarda el resultado indexado por // fnv1a64(entity_id) — la misma key que usa graph_sources al setear // node.user_data, asi que el render puede mirar directo por user_data. // Si una tabla falla, su entrada NO se inserta y se imprime un warning. @@ -73,7 +73,7 @@ struct TableCounts { // user_data hash (fnv1a64 del entity id) -> total filas tras filter_sql. // -1 indica error/ausencia. }; -bool tableview_refresh_counts(const char* ops_db, +bool node_groups_refresh_counts(const char* ops_db, std::unordered_map* out); // ---------------------------------------------------------------------------- @@ -81,11 +81,11 @@ bool tableview_refresh_counts(const char* ops_db, // ---------------------------------------------------------------------------- // Snapshot de la metadata relevante de un nodo Table. Caller-owned strings. -struct TableMetadata { +struct NodeGroupsMeta { std::string entity_id; std::string name; std::string duckdb_path; // tal como aparece en metadata (relativo o absoluto) - std::string duckdb_path_abs; // resuelto con tableview_resolve_path + std::string duckdb_path_abs; // resuelto con node_groups_resolve_path std::string table_name; std::string row_type; std::string id_column; @@ -98,17 +98,17 @@ struct TableMetadata { // Lee la metadata del nodo Table (entidad type_ref='Table' con id=`entity_id`). // Devuelve false si no existe o falla el JSON. Aplica defaults razonables a // los campos faltantes (id_column='id', label_column='name'). -bool tableview_get_metadata(const char* ops_db, const char* entity_id, - TableMetadata* out); +bool node_groups_get_metadata(const char* ops_db, const char* entity_id, + NodeGroupsMeta* out); // Persiste el flag `expanded` en la metadata. Idempotente. Devuelve false // en error de IO/SQL. -bool tableview_set_expanded(const char* ops_db, const char* entity_id, +bool node_groups_set_expanded(const char* ops_db, const char* entity_id, bool expanded); // Sobrescribe el array `columns` en la metadata. Llamar tras editar columnas -// desde la UI o tras tableview_create cuando se descubren columnas. -bool tableview_set_columns(const char* ops_db, const char* entity_id, +// desde la UI o tras node_groups_create cuando se descubren columnas. +bool node_groups_set_columns(const char* ops_db, const char* entity_id, const std::vector& columns); // Promueve una fila de DuckDB a entidad del grafo. Idempotente: si ya existe @@ -123,7 +123,7 @@ bool tableview_set_columns(const char* ops_db, const char* entity_id, // Si table_entity_id no es nulo/vacio, inserta tambien una relacion // CONTAINS_ROW (idempotente) entre la tabla y la nueva entidad para que el // viewport pinte la arista de pertenencia. -bool tableview_promote_row(const char* ops_db, +bool node_groups_promote_row(const char* ops_db, const char* table_entity_id, const char* duckdb_path, const char* duck_table, @@ -134,9 +134,9 @@ bool tableview_promote_row(const char* ops_db, // Borra la entidad. La fila DuckDB sigue intacta. Devuelve true si la fila // no existia (no-op idempotente). -bool tableview_demote_row(const char* ops_db, const char* entity_id); +bool node_groups_demote_row(const char* ops_db, const char* entity_id); -// Tipos de fichero soportados por tableview_ingest_file. +// Tipos de fichero soportados por node_groups_ingest_file. enum IngestKind { INGEST_AUTO = 0, // detecta por extension INGEST_CSV = 1, @@ -147,15 +147,15 @@ enum IngestKind { // Importa un fichero CSV/Parquet/JSON al `.duckdb`. Crea el .duckdb si no // existe. Si la tabla destino existe, falla (no sobrescribe — explicit fail). // Por defecto INGEST_AUTO inspecciona la extension del path. -bool tableview_ingest_file(const char* duckdb_path, +bool node_groups_ingest_file(const char* duckdb_path, const char* file_path, const char* dest_table, IngestKind kind, std::string* out_error); // Lista los nombres de columna de la tabla DuckDB. Para popular la lista -// `columns` por defecto en tableview_create. -bool tableview_list_columns(const char* duckdb_path, +// `columns` por defecto en node_groups_create. +bool node_groups_list_columns(const char* duckdb_path, const char* duck_table, std::vector* out); diff --git a/views.cpp b/views.cpp index e180d1e..e7a4506 100644 --- a/views.cpp +++ b/views.cpp @@ -306,15 +306,15 @@ void views_toolbar(AppState& app) { { char btn[64]; std::snprintf(btn, sizeof(btn), TI_TABLE " Tables (%zu)", - app.table_windows.size()); + app.node_groups_windows.size()); if (button(btn, ButtonVariant::Subtle)) { ImGui::OpenPopup("##tables_menu"); } if (ImGui::BeginPopup("##tables_menu")) { - if (app.table_windows.empty()) { - ImGui::TextDisabled("(no expanded tables)"); + if (app.node_groups_windows.empty()) { + ImGui::TextDisabled("(no open NodeGroups)"); } else { - for (auto& kv : app.table_windows) { + for (auto& kv : app.node_groups_windows) { bool checked = kv.second.open; char lbl[160]; std::snprintf(lbl, sizeof(lbl), "%s (%lld rows)", @@ -326,7 +326,7 @@ void views_toolbar(AppState& app) { } ImGui::Separator(); if (ImGui::MenuItem(TI_X " Collapse all")) { - for (auto& kv : app.table_windows) kv.second.open = false; + for (auto& kv : app.node_groups_windows) kv.second.open = false; } } ImGui::EndPopup(); @@ -1873,12 +1873,12 @@ void views_table(AppState& app) { // Table node UI fase 2 (issue 0011) — ventana expandida + import // ---------------------------------------------------------------------------- -void views_table_windows_sync(AppState& app, const char* ops_db) { +void views_node_groups_windows_sync(AppState& app, const char* ops_db) { if (!app.graph || !ops_db) return; GraphData& g = *app.graph; // Construir set de Tables expandidas con su metadata fresca. - std::unordered_map live; + std::unordered_map live; for (int i = 0; i < g.node_count; ++i) { const GraphNode& n = g.nodes[i]; if (n.type_id >= (uint16_t)g.type_count) continue; @@ -1906,8 +1906,8 @@ void views_table_windows_sync(AppState& app, const char* ops_db) { const unsigned char* p = sqlite3_column_text(st, 0); if (!p) continue; std::string id = (const char*)p; - TableMetadata meta; - if (tableview_get_metadata(ops_db, id.c_str(), &meta)) { + NodeGroupsMeta meta; + if (node_groups_get_metadata(ops_db, id.c_str(), &meta)) { live.emplace(id, std::move(meta)); } } @@ -1915,8 +1915,8 @@ void views_table_windows_sync(AppState& app, const char* ops_db) { sqlite3_close(db); // Quitar las que ya no estan expanded. - for (auto it = app.table_windows.begin(); it != app.table_windows.end(); ) { - if (live.find(it->first) == live.end()) it = app.table_windows.erase(it); + for (auto it = app.node_groups_windows.begin(); it != app.node_groups_windows.end(); ) { + if (live.find(it->first) == live.end()) it = app.node_groups_windows.erase(it); else ++it; } // Anadir las nuevas o refrescar metadata. Tras cualquier sync forzamos @@ -1925,7 +1925,7 @@ void views_table_windows_sync(AppState& app, const char* ops_db) { // promote/demote/import — donde el flag promoted de cada fila puede // haber cambiado). for (auto& kv : live) { - auto& w = app.table_windows[kv.first]; + auto& w = app.node_groups_windows[kv.first]; bool was_present = !w.meta.entity_id.empty(); w.meta = std::move(kv.second); w.open = true; @@ -1938,18 +1938,18 @@ void views_table_windows_sync(AppState& app, const char* ops_db) { } } -void views_table_window(AppState& app) { - if (app.table_windows.empty()) return; +void views_node_groups_window(AppState& app) { + if (app.node_groups_windows.empty()) return; GraphData* g = app.graph; GraphViewportState* vp = app.viewport; - for (auto& kv : app.table_windows) { - TableMetadata& m = kv.second.meta; - AppState::TableWindowState& w = kv.second; + for (auto& kv : app.node_groups_windows) { + NodeGroupsMeta& m = kv.second.meta; + AppState::NodeGroupsWindowState& w = kv.second; char title[160]; - std::snprintf(title, sizeof(title), TI_TABLE " %s##te_%s", - m.name.empty() ? "Table" : m.name.c_str(), + std::snprintf(title, sizeof(title), TI_TABLE " NodeGroups: %s##te_%s", + m.name.empty() ? "(unnamed)" : m.name.c_str(), m.entity_id.c_str()); ImGui::SetNextWindowSize(ImVec2(640, 460), ImGuiCond_FirstUseEver); if (!ImGui::Begin(title, &w.open)) { ImGui::End(); continue; } @@ -1987,7 +1987,7 @@ void views_table_window(AppState& app) { // avanzamos offset. const int64_t page_size = 200; for (int64_t i = 0; i < (int64_t)w.page.size(); ++i) { - const TablePageRow& row = w.page[i]; + const NodeGroupsRow& row = w.page[i]; ImGui::TableNextRow(); ImGui::PushID((int)(w.offset + i)); @@ -2080,7 +2080,7 @@ void views_table_window(AppState& app) { } // Cerrar la ventana = expanded=false. Lo procesa main.cpp leyendo - // table_windows y comparando `open`. + // node_groups_windows y comparando `open`. } bool views_import_dataset_modal(AppState& app) { @@ -2132,7 +2132,7 @@ bool views_import_dataset_modal(AppState& app) { // Table node overlay (issue 0010) // ---------------------------------------------------------------------------- -void views_table_overlay(AppState& app) { +void views_node_groups_overlay(AppState& app) { if (!app.graph || !app.viewport) return; GraphData& g = *app.graph; if (g.type_count == 0) return; @@ -2162,8 +2162,8 @@ void views_table_overlay(AppState& app) { if (vy < wmin.y - 100 || vy > wmax.y + 100) continue; int64_t count = -1; - auto it = app.table_node_counts.find(n.user_data); - if (it != app.table_node_counts.end()) count = it->second; + auto it = app.node_groups_counts.find(n.user_data); + if (it != app.node_groups_counts.end()) count = it->second; if (count < 0) continue; char buf[64]; diff --git a/views.h b/views.h index 0efef1e..8b6ca1c 100644 --- a/views.h +++ b/views.h @@ -5,7 +5,7 @@ #include "types_registry.h" #include "entity_ops.h" -#include "tableview.h" +#include "node_groups.h" #include #include @@ -158,23 +158,24 @@ struct AppState { // ---- Table node (issue 0010) ------------------------------------------ // Cache de conteo de filas por nodo Table indexado por user_data hash. // Refrescado tras load_input y tras mutaciones que afecten a Tables. - std::unordered_map table_node_counts; + std::unordered_map node_groups_counts; - // ---- Table node UI fase 2 (issue 0011) -------------------------------- - // Estado runtime por ventana de Table expandida. Una entrada por - // entity_id de Table que el usuario haya expandido. La ventana se cierra - // cuando set_expanded(false) — ya sea desde context menu o cerrando la - // ImGui window (que pone el flag a false automaticamente). - struct TableWindowState { - TableMetadata meta; // refrescada cada vez que entity cambia - int64_t total_rows = 0; - int64_t offset = 0; - std::vector page; - bool page_dirty = true; - bool open = true; // bound a ImGui::Begin - std::string last_error; // ultimo error de query (vacio = OK) + // ---- NodeGroups window (issue 0011, renombrado en 0036a) -------------- + // Estado runtime por ventana de NodeGroups (un Table-typed expandido). + // Una entrada por entity_id de Table que el usuario haya expandido. La + // ventana se cierra cuando set_expanded(false) — ya sea desde context + // menu o cerrando la ImGui window (que pone el flag a false + // automaticamente). + struct NodeGroupsWindowState { + NodeGroupsMeta meta; // refrescada cada vez que entity cambia + int64_t total_rows = 0; + int64_t offset = 0; + std::vector page; + bool page_dirty = true; + bool open = true; // bound a ImGui::Begin + std::string last_error; // ultimo error de query (vacio = OK) }; - std::unordered_map table_windows; + std::unordered_map node_groups_windows; // Triggers consumidos por main.cpp tras click en filas. bool want_promote_row = false; @@ -196,9 +197,9 @@ struct AppState { bool want_import = false; std::string import_error; - // Toggle expanded desde context menu del viewport. - bool want_toggle_expanded = false; - std::string toggle_expanded_id; + // Toggle NodeGroups window desde context menu del viewport. + bool want_toggle_nodegroups = false; + std::string toggle_nodegroups_id; // ---- Table view (issue 0004) ------------------------------------------- // Vista tabular dockeable. Tabs por type_ref del grafo activo + opcional @@ -335,33 +336,34 @@ EntityRecord views_inspector_build_record(const AppState& app); // al cambiar de proyecto. void views_inspector_clear_draft(AppState& app); -// ---- Table node UI fase 2 (issue 0011) ---------------------------------- +// ---- NodeGroups window (issue 0011, renombrado en 0036a) ---------------- -// Renderiza una ventana ImGui dockeable por cada Table en table_windows -// con `open=true`. Cabecera con nombres de columnas. Filas paginadas con -// ImGuiListClipper consumiendo el page cache; al cambiar el offset, marca -// dirty para que main.cpp refresque via tableview_page. Doble click en -// fila no promovida -> setea promote_table_id/promote_row_id; promovida -// -> focus_entity_id. Cerrar la ventana setea expanded=false en BD. -void views_table_window(AppState& app); +// Renderiza una ventana ImGui dockeable por cada NodeGroups en +// node_groups_windows con `open=true`. Cabecera con nombres de columnas. +// Filas paginadas con ImGuiListClipper consumiendo el page cache; al +// cambiar el offset, marca dirty para que main.cpp refresque via +// node_groups_page. Doble click en fila no promovida -> setea +// promote_table_id/promote_row_id; promovida -> focus_entity_id. Cerrar la +// ventana setea expanded=false en BD. +void views_node_groups_window(AppState& app); // Modal "Import dataset..." — formulario para crear una tabla DuckDB // desde CSV/Parquet/JSON y registrar el nodo Table correspondiente. bool views_import_dataset_modal(AppState& app); -// Sincroniza table_windows con la metadata.expanded de cada nodo Table. -// Llamar tras load + tras mutaciones que cambien expanded. Crea entradas -// para nuevos expanded y borra las que ya no aplican. -void views_table_windows_sync(AppState& app, const char* ops_db); +// Sincroniza node_groups_windows con la metadata.expanded de cada nodo +// Table. Llamar tras load + tras mutaciones que cambien expanded. Crea +// entradas para nuevos expanded y borra las que ya no aplican. +void views_node_groups_windows_sync(AppState& app, const char* ops_db); // ---- Table node overlay (issue 0010) ------------------------------------ // Dibuja un overlay rectangulo redondeado sobre cada nodo `Table` del grafo -// con etiqueta "Table · N rows" leyendo de app.table_node_counts. Llamar +// con etiqueta "Table · N rows" leyendo de app.node_groups_counts. Llamar // despues de graph_viewport(...) — usa GetItemRectMin/Max + GetWindowDrawList // del item viewport. No interactua con eventos; el hit-testing del nodo // sigue usandolo el viewport circular de fondo. -void views_table_overlay(AppState& app); +void views_node_groups_overlay(AppState& app); // ---- Table view (issue 0004) --------------------------------------------