From 3d7c5bc0a1e8065b2496f17574ce71e16d023832 Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 15 May 2026 14:39:51 +0200 Subject: [PATCH] migrate function/type/app tables to data_table_cpp_viz (issue 0081-J) - Replace table_view() calls in draw_recent_functions, draw_apps_list, draw_analysis_list, draw_types_list, and vaults panel with data_table::render() via fn_table_viz static lib. - Migrate Monitor sub-tabs Top Functions, Violations, Copied Code to data_table::render() with persistent State per panel. - Keep Recent Executions and Failed Functions as custom ImGui tables (per-cell coloring + tooltips not supported by data_table). - Layout-splitter tables (kpi_grid, chart_grid, monitor_kpi, proj_layout, explorer_layout) intentionally not migrated. - CMakeLists: target_link_libraries(registry_dashboard PRIVATE fn_table_viz). - app.md uses_functions += data_table_cpp_viz + full fn_table_viz stack. Co-Authored-By: Claude Sonnet 4.6 --- CMakeLists.txt | 5 + app.md | 12 ++ views.cpp | 465 ++++++++++++++++++++++++++++++++++--------------- 3 files changed, 343 insertions(+), 139 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b1ca230..36f00c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -53,6 +53,11 @@ target_include_directories(registry_dashboard PRIVATE target_link_libraries(registry_dashboard PRIVATE SQLite::SQLite3) +# Issue 0081-J: data_table::render via fn_table_viz static lib +if(TARGET fn_table_viz) + target_link_libraries(registry_dashboard PRIVATE fn_table_viz) +endif() + # Sockets: ws2_32 on Windows, nothing extra on Linux if(WIN32) target_link_libraries(registry_dashboard PRIVATE ws2_32) diff --git a/app.md b/app.md index 3ed767b..b7e8800 100644 --- a/app.md +++ b/app.md @@ -11,6 +11,18 @@ uses_functions: - pie_chart_cpp_viz - table_view_cpp_viz - sparkline_cpp_viz + # data_table stack (issue 0081-J) + - data_table_cpp_viz + - viz_render_cpp_viz + - compute_stage_cpp_core + - compute_pipeline_cpp_core + - tql_emit_cpp_core + - tql_apply_cpp_core + - tql_to_sql_cpp_core + - lua_engine_cpp_core + - join_tables_cpp_core + - auto_detect_type_cpp_core + - compute_column_stats_cpp_core # core (dashboard primitives) - dashboard_panel_cpp_core - dashboard_grid_cpp_core diff --git a/views.cpp b/views.cpp index 3ab1f88..11c49a5 100644 --- a/views.cpp +++ b/views.cpp @@ -9,6 +9,8 @@ #include "viz/pie_chart.h" #include "viz/table_view.h" #include "viz/sparkline.h" +#include "viz/data_table.h" +#include "core/data_table_types.h" #include "core/icons_tabler.h" #include "core/dashboard_panel.h" #include "core/dashboard_grid.h" @@ -103,6 +105,191 @@ static std::string format_ts_relative(long long ts) { } static fn_ui::ProcessRunner g_add_runner; +// --------------------------------------------------------------------------- +// data_table::State — persistent per panel (issue 0081-J) +// --------------------------------------------------------------------------- +// One State per data-table panel. Must NOT be stack-local (see data_table.md +// Gotchas: "State no stack-local"). +static data_table::State g_dt_recent_funcs; +static data_table::State g_dt_apps; +static data_table::State g_dt_analysis; +static data_table::State g_dt_types; +static data_table::State g_dt_vaults; +static data_table::State g_dt_top_fn; +static data_table::State g_dt_violations; +static data_table::State g_dt_copies; + +// --------------------------------------------------------------------------- +// Helpers: build a data_table::TableInput from registry row vectors +// --------------------------------------------------------------------------- + +// Build a flat cells vector (row-major) + keep string backing alive. +// Returns backing storage; `ti` is populated in-place. +static std::vector make_cells_recent_funcs( + const std::vector& funcs, + data_table::TableInput& ti) +{ + ti.name = "functions"; + ti.headers = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::Date, + }; + ti.rows = static_cast(funcs.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(funcs.size() * ti.cols); + for (const auto& f : funcs) { + backing.push_back(f.name); + backing.push_back(f.lang); + backing.push_back(f.domain); + backing.push_back(f.kind); + backing.push_back(f.purity); + backing.push_back(f.tested ? "yes" : "no"); + backing.push_back(f.created_at.substr(0, 10)); + } + return backing; +} + +static std::vector make_cells_apps( + const std::vector& apps, + data_table::TableInput& ti) +{ + ti.name = "apps"; + ti.headers = {"Name", "Lang", "Domain", "Framework", "Git", "Description"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(apps.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(apps.size() * ti.cols); + for (const auto& a : apps) { + std::string git_status; + if (!a.repo_url.empty()) { + git_status = "remote"; + } else if (!a.dir_path.empty()) { + std::error_code ec; + if (std::filesystem::exists( + std::filesystem::path(a.dir_path) / ".git", ec)) + git_status = "local"; + else + git_status = "-"; + } else { + git_status = "-"; + } + backing.push_back(a.name); + backing.push_back(a.lang); + backing.push_back(a.domain); + backing.push_back(a.framework); + backing.push_back(git_status); + backing.push_back(a.description); + } + return backing; +} + +static std::vector make_cells_analysis( + const std::vector& analyses, + data_table::TableInput& ti) +{ + ti.name = "analysis"; + ti.headers = {"Name", "Lang", "Domain", "Description"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(analyses.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(analyses.size() * ti.cols); + for (const auto& a : analyses) { + backing.push_back(a.name); + backing.push_back(a.lang); + backing.push_back(a.domain); + backing.push_back(a.description); + } + return backing; +} + +static std::vector make_cells_types( + const std::vector& types, + data_table::TableInput& ti) +{ + ti.name = "types"; + ti.headers = {"Name", "Lang", "Domain", "Algebraic", "Description"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(types.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(types.size() * ti.cols); + for (const auto& t : types) { + backing.push_back(t.name); + backing.push_back(t.lang); + backing.push_back(t.domain); + backing.push_back(t.algebraic); + backing.push_back(t.description); + } + return backing; +} + +static std::vector make_cells_vaults( + const std::vector& vaults, + data_table::TableInput& ti) +{ + ti.name = "vaults"; + ti.headers = {"Name", "Path", "Symlink", "Description"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(vaults.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(vaults.size() * ti.cols); + for (const auto& v : vaults) { + backing.push_back(v.name); + backing.push_back(v.path); + backing.push_back(v.symlink ? "yes" : "no"); + backing.push_back(v.description); + } + return backing; +} + +// Converts backing vector to flat const char* array for TableInput.cells. +// `ptrs` must outlive the render() call. +static void cells_to_ptrs(const std::vector& backing, + std::vector& ptrs) +{ + ptrs.resize(backing.size()); + for (size_t i = 0; i < backing.size(); i++) + ptrs[i] = backing[i].c_str(); +} + // Add modal state enum class AddKind : int { App = 0, Analysis = 1, Vault = 2 }; static bool g_show_add = false; @@ -275,21 +462,15 @@ void draw_recent_functions(const std::vector& funcs) { "Run 'fn index' to populate the registry"); return; } - const char* headers[] = {"Name", "Lang", "Domain", "Kind", "Purity", "Tested", "Created"}; - constexpr int cols = 7; - std::vector cell_strings; - cell_strings.reserve(funcs.size() * cols); - for (auto& f : funcs) { - cell_strings.push_back(f.name); - cell_strings.push_back(f.lang); - cell_strings.push_back(f.domain); - cell_strings.push_back(f.kind); - cell_strings.push_back(f.purity); - cell_strings.push_back(f.tested ? "yes" : "no"); - cell_strings.push_back(f.created_at.substr(0, 10)); - } - auto cells = to_cstr(cell_strings); - table_view("##recent", headers, cols, cells.data(), static_cast(funcs.size())); + data_table::TableInput ti; + auto backing = make_cells_recent_funcs(funcs, ti); + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##recent_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_recent_funcs", {ti}, g_dt_recent_funcs); + ImGui::EndChild(); } void draw_apps_list(const std::vector& apps) { @@ -298,37 +479,15 @@ void draw_apps_list(const std::vector& apps) { "Use the + Add button above or run 'fn sync'"); return; } - const char* headers[] = {"Name", "Lang", "Domain", "Framework", "Git", "Description"}; - constexpr int cols = 6; - std::vector cell_strings; - cell_strings.reserve(apps.size() * cols); - for (auto& a : apps) { - cell_strings.push_back(a.name); - cell_strings.push_back(a.lang); - cell_strings.push_back(a.domain); - cell_strings.push_back(a.framework); - // Indicador de git: tiene repo remoto (gitea), solo local, o ninguno. - // - "remote": repo_url poblado en el frontmatter del app.md - // - "local": hay .git/ en dir_path pero sin repo_url - // - "-": ni .git ni repo_url - std::string git_status; - if (!a.repo_url.empty()) { - git_status = "remote"; - } else if (!a.dir_path.empty()) { - std::error_code ec; - if (std::filesystem::exists(std::filesystem::path(a.dir_path) / ".git", ec)) { - git_status = "local"; - } else { - git_status = "-"; - } - } else { - git_status = "-"; - } - cell_strings.push_back(git_status); - cell_strings.push_back(a.description); - } - auto cells = to_cstr(cell_strings); - table_view("##apps", headers, cols, cells.data(), static_cast(apps.size())); + data_table::TableInput ti; + auto backing = make_cells_apps(apps, ti); + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##apps_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_apps", {ti}, g_dt_apps); + ImGui::EndChild(); } void draw_analysis_list(const std::vector& analyses) { @@ -337,18 +496,15 @@ void draw_analysis_list(const std::vector& analyses) { "Use the + Add button above with kind = Analysis"); return; } - const char* headers[] = {"Name", "Lang", "Domain", "Description"}; - constexpr int cols = 4; - std::vector cell_strings; - cell_strings.reserve(analyses.size() * cols); - for (auto& a : analyses) { - cell_strings.push_back(a.name); - cell_strings.push_back(a.lang); - cell_strings.push_back(a.domain); - cell_strings.push_back(a.description); - } - auto cells = to_cstr(cell_strings); - table_view("##analysis", headers, cols, cells.data(), static_cast(analyses.size())); + data_table::TableInput ti; + auto backing = make_cells_analysis(analyses, ti); + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##analysis_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_analysis", {ti}, g_dt_analysis); + ImGui::EndChild(); } void draw_types_list(const std::vector& types) { @@ -357,19 +513,15 @@ void draw_types_list(const std::vector& types) { "Types are indexed from the registry alongside functions"); return; } - const char* headers[] = {"Name", "Lang", "Domain", "Algebraic", "Description"}; - constexpr int cols = 5; - std::vector cell_strings; - cell_strings.reserve(types.size() * cols); - for (auto& t : types) { - cell_strings.push_back(t.name); - cell_strings.push_back(t.lang); - cell_strings.push_back(t.domain); - cell_strings.push_back(t.algebraic); - cell_strings.push_back(t.description); - } - auto cells = to_cstr(cell_strings); - table_view("##types", headers, cols, cells.data(), static_cast(types.size())); + data_table::TableInput ti; + auto backing = make_cells_types(types, ti); + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##types_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_types", {ti}, g_dt_types); + ImGui::EndChild(); } // --------------------------------------------------------------------------- @@ -686,27 +838,44 @@ void draw_monitor(RegistryData& data) { if (cu.top_functions.empty()) { ImGui::TextDisabled("No function calls recorded yet. Hook fires on next session."); } else { - const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders - | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##monitor_top_fn", 6, tf, ImVec2(0, 0))) { - ImGui::TableSetupColumn("Function ID"); - ImGui::TableSetupColumn("Calls"); - ImGui::TableSetupColumn("7d"); - ImGui::TableSetupColumn("Errors"); - ImGui::TableSetupColumn("Error %"); - ImGui::TableSetupColumn("Mean ms"); - ImGui::TableHeadersRow(); - for (const auto& r : cu.top_functions) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(r.function_id.c_str()); - ImGui::TableSetColumnIndex(1); ImGui::Text("%d", r.calls_total); - ImGui::TableSetColumnIndex(2); ImGui::Text("%d", r.calls_7d); - ImGui::TableSetColumnIndex(3); ImGui::Text("%d", r.errors_total); - ImGui::TableSetColumnIndex(4); ImGui::Text("%.1f%%", r.error_rate * 100.0); - ImGui::TableSetColumnIndex(5); ImGui::Text("%.0f", r.mean_duration_ms); - } - ImGui::EndTable(); + // Build TableInput for data_table::render + data_table::TableInput ti; + ti.name = "top_functions"; + ti.headers = {"Function ID", "Calls", "7d", "Errors", "Error %", "Mean ms"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::Int, + data_table::ColumnType::Int, + data_table::ColumnType::Int, + data_table::ColumnType::Float, + data_table::ColumnType::Float, + }; + ti.rows = static_cast(cu.top_functions.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(cu.top_functions.size() * 6); + char buf[32]; + for (const auto& r : cu.top_functions) { + backing.push_back(r.function_id); + std::snprintf(buf, sizeof(buf), "%d", r.calls_total); + backing.push_back(buf); + std::snprintf(buf, sizeof(buf), "%d", r.calls_7d); + backing.push_back(buf); + std::snprintf(buf, sizeof(buf), "%d", r.errors_total); + backing.push_back(buf); + std::snprintf(buf, sizeof(buf), "%.1f%%", r.error_rate * 100.0); + backing.push_back(buf); + std::snprintf(buf, sizeof(buf), "%.0f", r.mean_duration_ms); + backing.push_back(buf); } + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##top_fn_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_top_fn", {ti}, g_dt_top_fn); + ImGui::EndChild(); } ImGui::EndTabItem(); } @@ -714,25 +883,35 @@ void draw_monitor(RegistryData& data) { if (cu.recent_violations.empty()) { ImGui::TextDisabled("No antipattern violations detected."); } else { - const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders - | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##monitor_viol", 5, tf, ImVec2(0, 0))) { - ImGui::TableSetupColumn("When"); - ImGui::TableSetupColumn("Rule"); - ImGui::TableSetupColumn("Severity"); - ImGui::TableSetupColumn("Function"); - ImGui::TableSetupColumn("Snippet"); - ImGui::TableHeadersRow(); - for (const auto& r : cu.recent_violations) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(format_ts(r.ts).c_str()); - ImGui::TableSetColumnIndex(1); ImGui::TextUnformatted(r.rule_id.c_str()); - ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(r.severity.c_str()); - ImGui::TableSetColumnIndex(3); ImGui::TextUnformatted(r.function_id.c_str()); - ImGui::TableSetColumnIndex(4); ImGui::TextUnformatted(r.command_snippet.c_str()); - } - ImGui::EndTable(); + data_table::TableInput ti; + ti.name = "violations"; + ti.headers = {"When", "Rule", "Severity", "Function", "Snippet"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(cu.recent_violations.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(cu.recent_violations.size() * 5); + for (const auto& r : cu.recent_violations) { + backing.push_back(format_ts(r.ts)); + backing.push_back(r.rule_id); + backing.push_back(r.severity); + backing.push_back(r.function_id); + backing.push_back(r.command_snippet); } + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##viol_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_violations", {ti}, g_dt_violations); + ImGui::EndChild(); } ImGui::EndTabItem(); } @@ -793,25 +972,37 @@ void draw_monitor(RegistryData& data) { if (cu.copies.empty()) { ImGui::TextDisabled("No copied code detected. Run `fn doctor copied-code` or `call_monitor copied-code`."); } else { - const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders - | ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY; - if (ImGui::BeginTable("##monitor_copies", 5, tf, ImVec2(0, 0))) { - ImGui::TableSetupColumn("Kind"); - ImGui::TableSetupColumn("Sim"); - ImGui::TableSetupColumn("App File"); - ImGui::TableSetupColumn("App Function"); - ImGui::TableSetupColumn("Registry ID"); - ImGui::TableHeadersRow(); - for (const auto& r : cu.copies) { - ImGui::TableNextRow(); - ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(r.kind.c_str()); - ImGui::TableSetColumnIndex(1); ImGui::Text("%.2f", r.similarity); - ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(r.app_file.c_str()); - ImGui::TableSetColumnIndex(3); ImGui::TextUnformatted(r.app_function.c_str()); - ImGui::TableSetColumnIndex(4); ImGui::TextUnformatted(r.registry_id.c_str()); - } - ImGui::EndTable(); + data_table::TableInput ti; + ti.name = "copies"; + ti.headers = {"Kind", "Sim", "App File", "App Function", "Registry ID"}; + ti.types = { + data_table::ColumnType::String, + data_table::ColumnType::Float, + data_table::ColumnType::String, + data_table::ColumnType::String, + data_table::ColumnType::String, + }; + ti.rows = static_cast(cu.copies.size()); + ti.cols = static_cast(ti.headers.size()); + + std::vector backing; + backing.reserve(cu.copies.size() * 5); + char buf[32]; + for (const auto& r : cu.copies) { + backing.push_back(r.kind); + std::snprintf(buf, sizeof(buf), "%.2f", r.similarity); + backing.push_back(buf); + backing.push_back(r.app_file); + backing.push_back(r.app_function); + backing.push_back(r.registry_id); } + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + + ImGui::BeginChild("##copies_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_copies", {ti}, g_dt_copies); + ImGui::EndChild(); } ImGui::EndTabItem(); } @@ -916,18 +1107,14 @@ void draw_projects_list(RegistryData& data) { empty_state("( no data )", "No vaults in this project", "Use the + Add button with kind = Vault (requires a project)"); } else { - const char* headers[] = {"Name", "Path", "Symlink", "Description"}; - std::vector cells; - cells.reserve(d.vaults.size() * 4); - for (auto& v : d.vaults) { - cells.push_back(v.name); - cells.push_back(v.path); - cells.push_back(v.symlink ? "yes" : "no"); - cells.push_back(v.description); - } - auto cp = to_cstr(cells); - table_view("##vaults", headers, 4, cp.data(), - static_cast(d.vaults.size())); + data_table::TableInput ti; + auto backing = make_cells_vaults(d.vaults, ti); + std::vector ptrs; + cells_to_ptrs(backing, ptrs); + ti.cells = ptrs.data(); + ImGui::BeginChild("##vaults_dt_wrap", ImVec2(-1, -1)); + data_table::render("##dt_vaults", {ti}, g_dt_vaults); + ImGui::EndChild(); } ImGui::EndTabItem(); }