migrate Monitor Recent Executions + Failed Functions to data_table::render with Badge/Duration renderers (issue 0081-J)

- Replace ##monitor_recent BeginTable (6 cols) with data_table::render:
  - Duration renderer on duration_ms col (warn=500ms, error=2000ms)
  - Badge renderer on Status col: ok=#22c55e, error=#ef4444, running=#3b82f6, timeout=#f59e0b
  - success bool encoded as "ok"/"error" string for badge matching
- Replace ##monitor_failed_fns BeginTable (5 cols) with data_table::render:
  - Badge renderer on Error Class col: sqlite/network/timeout/not_found/auth/parse/io/permission/unknown
  - empty error_class normalized to "unknown" for badge match
- Add g_dt_monitor_recent + g_dt_monitor_failed persistent State members
- Remove all inline coloring helpers for these two tables (covered by renderers)
- fn doctor cpp-apps: registry_dashboard BeginTable inline count 7 -> 5
  (5 remaining are layout splitters: kpi_grid/chart_grid/monitor_kpi/proj_layout/explorer_layout)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 16:43:51 +02:00
parent 55a9c07e46
commit 3f8c12db89
+144 -97
View File
@@ -118,6 +118,8 @@ static data_table::State g_dt_vaults;
static data_table::State g_dt_top_fn; static data_table::State g_dt_top_fn;
static data_table::State g_dt_violations; static data_table::State g_dt_violations;
static data_table::State g_dt_copies; static data_table::State g_dt_copies;
static data_table::State g_dt_monitor_recent; // issue 0081-J
static data_table::State g_dt_monitor_failed; // issue 0081-J
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Helpers: build a data_table::TableInput from registry row vectors // Helpers: build a data_table::TableInput from registry row vectors
@@ -769,68 +771,83 @@ void draw_monitor(RegistryData& data) {
if (cu.recent_executions.empty()) { if (cu.recent_executions.empty()) {
ImGui::TextDisabled("No executions in this window. Try widening (7d/30d/All) or wait for the next call."); ImGui::TextDisabled("No executions in this window. Try widening (7d/30d/All) or wait for the next call.");
} else { } else {
const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders // Build TableInput for data_table::render (issue 0081-J).
| ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY; // Columns: When | Function | Tool | Duration(ms) | Status
if (ImGui::BeginTable("##monitor_recent", 6, tf, ImVec2(0, 0))) { // Status = "ok" | "error" (from r.success) → Badge renderer.
ImGui::TableSetupColumn("When"); // Duration → Duration renderer (warn=500ms, error=2000ms).
ImGui::TableSetupColumn("Function"); data_table::TableInput ti;
ImGui::TableSetupColumn("Tool"); ti.name = "monitor_recent";
ImGui::TableSetupColumn("ms"); ti.headers = {"When", "Function", "Tool", "Duration", "Status"};
ImGui::TableSetupColumn("OK"); ti.types = {
ImGui::TableSetupColumn("Error"); data_table::ColumnType::String,
ImGui::TableHeadersRow(); data_table::ColumnType::String,
for (const auto& r : cu.recent_executions) { data_table::ColumnType::String,
if (g_recent_only_registry && r.function_id.empty()) continue; data_table::ColumnType::Float,
ImGui::TableNextRow(); data_table::ColumnType::String,
ImGui::TableSetColumnIndex(0); };
ImGui::TextUnformatted(format_ts(r.ts).c_str());
ImGui::TableSetColumnIndex(1); // column_specs: parallel to headers (5 specs)
if (!r.function_id.empty()) { ti.column_specs.resize(5);
// Call de registry — destacada en color normal. // col 0 When: Text (default)
ImGui::TextUnformatted(r.function_id.c_str()); ti.column_specs[0].renderer = data_table::CellRenderer::Text;
} else if (!r.command_snippet.empty()) { // col 1 Function: Text (default)
// Tool generica (Bash/heredoc): muestra prefijo `$` + snippet ti.column_specs[1].renderer = data_table::CellRenderer::Text;
// truncado en muted para distinguirla visualmente de calls registry. // col 2 Tool: Text (default)
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); ti.column_specs[2].renderer = data_table::CellRenderer::Text;
// Truncado visual a ~80 chars para no romper layout. // col 3 Duration: Duration renderer
char buf[88]; ti.column_specs[3].renderer = data_table::CellRenderer::Duration;
std::snprintf(buf, sizeof(buf), "$ %.80s%s", ti.column_specs[3].duration_warn_ms = 500.0f;
r.command_snippet.c_str(), ti.column_specs[3].duration_error_ms = 2000.0f;
r.command_snippet.size() > 80 ? "..." : ""); // col 4 Status: Badge renderer
ImGui::TextUnformatted(buf); ti.column_specs[4].renderer = data_table::CellRenderer::Badge;
// Hover tooltip con snippet completo (hasta 200 chars). ti.column_specs[4].badges = {
if (ImGui::IsItemHovered()) { {"ok", "#22c55e", "OK"},
ImGui::BeginTooltip(); {"error", "#ef4444", "Error"},
ImGui::PushTextWrapPos(560.0f); {"running", "#3b82f6", "Running"},
ImGui::TextUnformatted(r.command_snippet.c_str()); {"timeout", "#f59e0b", "Timeout"},
ImGui::PopTextWrapPos(); };
ImGui::EndTooltip();
} std::vector<std::string> backing;
ImGui::PopStyleColor(); backing.reserve(cu.recent_executions.size() * 5);
} else { char dur_buf[24];
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::text_muted); for (const auto& r : cu.recent_executions) {
ImGui::TextUnformatted("-"); if (g_recent_only_registry && r.function_id.empty()) continue;
ImGui::PopStyleColor(); // When
} backing.push_back(format_ts(r.ts));
ImGui::TableSetColumnIndex(2); // Function — registry call or $ snippet or "-"
ImGui::TextUnformatted(r.tool_used.c_str()); if (!r.function_id.empty()) {
ImGui::TableSetColumnIndex(3); backing.push_back(r.function_id);
ImGui::Text("%d", r.duration_ms); } else if (!r.command_snippet.empty()) {
ImGui::TableSetColumnIndex(4); char sbuf[88];
if (r.success) { std::snprintf(sbuf, sizeof(sbuf), "$ %.80s%s",
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::success); r.command_snippet.c_str(),
ImGui::TextUnformatted(TI_CHECK); r.command_snippet.size() > 80 ? "..." : "");
ImGui::PopStyleColor(); backing.push_back(sbuf);
} else { } else {
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error); backing.push_back("-");
ImGui::TextUnformatted(TI_X);
ImGui::PopStyleColor();
}
ImGui::TableSetColumnIndex(5);
ImGui::TextUnformatted(r.error_class.c_str());
} }
ImGui::EndTable(); // Tool
backing.push_back(r.tool_used);
// Duration (as numeric string for Float column)
std::snprintf(dur_buf, sizeof(dur_buf), "%d", r.duration_ms);
backing.push_back(dur_buf);
// Status
backing.push_back(r.success ? "ok" : "error");
} }
int nrows = 0;
for (const auto& r : cu.recent_executions)
if (!g_recent_only_registry || !r.function_id.empty()) nrows++;
ti.rows = nrows;
ti.cols = static_cast<int>(ti.headers.size());
std::vector<const char*> ptrs;
cells_to_ptrs(backing, ptrs);
ti.cells = ptrs.data();
ImGui::BeginChild("##monitor_recent_wrap", ImVec2(-1, -1));
data_table::render("##dt_monitor_recent", {ti}, g_dt_monitor_recent);
ImGui::EndChild();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
} }
@@ -926,45 +943,75 @@ void draw_monitor(RegistryData& data) {
if (failed.empty()) { if (failed.empty()) {
ImGui::TextDisabled("No registry-function failures in this window. Healthy."); ImGui::TextDisabled("No registry-function failures in this window. Healthy.");
} else { } else {
const ImGuiTableFlags tf = ImGuiTableFlags_RowBg | ImGuiTableFlags_Borders // Build TableInput for data_table::render (issue 0081-J).
| ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY; // Columns: When | Function | Tool | Error Class | Snippet
if (ImGui::BeginTable("##monitor_failed_fns", 5, tf, ImVec2(0, 0))) { // Error Class → Badge renderer with common error class colors.
ImGui::TableSetupColumn("When"); data_table::TableInput ti;
ImGui::TableSetupColumn("Function"); ti.name = "monitor_failed";
ImGui::TableSetupColumn("Tool"); ti.headers = {"When", "Function", "Tool", "Error Class", "Snippet"};
ImGui::TableSetupColumn("Error class"); ti.types = {
ImGui::TableSetupColumn("Error snippet"); data_table::ColumnType::String,
ImGui::TableHeadersRow(); data_table::ColumnType::String,
for (const auto* p : failed) { data_table::ColumnType::String,
const auto& r = *p; data_table::ColumnType::String,
ImGui::TableNextRow(); data_table::ColumnType::String,
ImGui::TableSetColumnIndex(0); ImGui::TextUnformatted(format_ts(r.ts).c_str()); };
ImGui::TableSetColumnIndex(1);
ImGui::PushStyleColor(ImGuiCol_Text, fn_tokens::colors::error); // column_specs: parallel to headers (5 specs)
ImGui::TextUnformatted(r.function_id.c_str()); ti.column_specs.resize(5);
ImGui::PopStyleColor(); // col 0 When: Text (default)
ImGui::TableSetColumnIndex(2); ImGui::TextUnformatted(r.tool_used.c_str()); ti.column_specs[0].renderer = data_table::CellRenderer::Text;
ImGui::TableSetColumnIndex(3); ImGui::TextUnformatted(r.error_class.c_str()); // col 1 Function: Text (default) — already in error context, no extra badge needed
ImGui::TableSetColumnIndex(4); ti.column_specs[1].renderer = data_table::CellRenderer::Text;
if (r.error_snippet.empty()) { // col 2 Tool: Text (default)
ImGui::TextDisabled("-"); ti.column_specs[2].renderer = data_table::CellRenderer::Text;
} else { // col 3 Error Class: Badge renderer
char buf[120]; ti.column_specs[3].renderer = data_table::CellRenderer::Badge;
std::snprintf(buf, sizeof(buf), "%.110s%s", ti.column_specs[3].badges = {
r.error_snippet.c_str(), {"sqlite", "#f59e0b", "SQLite"},
r.error_snippet.size() > 110 ? "..." : ""); {"network", "#3b82f6", "Network"},
ImGui::TextUnformatted(buf); {"timeout", "#f59e0b", "Timeout"},
if (ImGui::IsItemHovered()) { {"not_found", "#8b5cf6", "Not Found"},
ImGui::BeginTooltip(); {"auth", "#ef4444", "Auth"},
ImGui::PushTextWrapPos(560.0f); {"parse", "#ec4899", "Parse"},
ImGui::TextUnformatted(r.error_snippet.c_str()); {"io", "#06b6d4", "I/O"},
ImGui::PopTextWrapPos(); {"permission", "#ef4444", "Permission"},
ImGui::EndTooltip(); {"unknown", "#6b7280", "Unknown"},
} };
} // col 4 Snippet: Text (default)
ti.column_specs[4].renderer = data_table::CellRenderer::Text;
std::vector<std::string> backing;
backing.reserve(failed.size() * 5);
for (const auto* p : failed) {
const auto& r = *p;
backing.push_back(format_ts(r.ts));
backing.push_back(r.function_id);
backing.push_back(r.tool_used);
// error_class: normalize empty to "unknown" for badge matching
backing.push_back(r.error_class.empty() ? "unknown" : r.error_class);
// snippet: truncate to ~110 chars for table display
if (r.error_snippet.empty()) {
backing.push_back("-");
} else {
char sbuf[120];
std::snprintf(sbuf, sizeof(sbuf), "%.110s%s",
r.error_snippet.c_str(),
r.error_snippet.size() > 110 ? "..." : "");
backing.push_back(sbuf);
} }
ImGui::EndTable();
} }
ti.rows = static_cast<int>(failed.size());
ti.cols = static_cast<int>(ti.headers.size());
std::vector<const char*> ptrs;
cells_to_ptrs(backing, ptrs);
ti.cells = ptrs.data();
ImGui::BeginChild("##monitor_failed_wrap", ImVec2(-1, -1));
data_table::render("##dt_monitor_failed", {ti}, g_dt_monitor_failed);
ImGui::EndChild();
} }
ImGui::EndTabItem(); ImGui::EndTabItem();
} }