data_table: declarative cell renderers Phase 1 (Badge/Progress/Duration/Icon)

Adds TableInput.column_specs sidecar field enabling apps to declare Badge,
Progress, Duration and Icon renderers per column without writing ImGui inline.
Back-compat: apps without column_specs compile and behave identically.

- data_table_types.h: CellRenderer enum, BadgeRule, IconMapEntry, ColumnSpec types
- data_table.cpp: hex_to_imcolor helper, icon_name_to_glyph static map (~30 Tabler icons),
  draw_cell_custom dispatcher, integration in Stage-0 and Stage-N cell loops and draw_extra_panel
- Bump version 1.0.0 -> 1.1.0 with capability growth log
- cpp/tests/test_column_specs.cpp: 5 smoke/linker tests (back-compat + 4 renderer types)
- cpp/tests/CMakeLists.txt: register test_column_specs target linked against fn_table_viz
- types/core/{cell_renderer,badge_rule,icon_map_entry,column_spec}.md: registry type mds
- docs/capabilities/data_table_renderers.md: canonical doc with end-to-end examples
- docs/capabilities/INDEX.md: added data-table-renderers group

All tests green: test_column_specs 5/5, test_fn_table_viz_smoke 8/8,
tql_emit 41/41, tql_apply 88/88, Wave-1 tests 8/8.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-15 16:38:24 +02:00
parent 7ecbee1175
commit 380a7a8f35
11 changed files with 4924 additions and 5 deletions
+55
View File
@@ -1,6 +1,7 @@
// data_table_types — types compartidos del stack TQL (Table Query Language).
// Promovido al registry desde cpp/apps/primitives_gallery/playground/tables/.
// Ver issue 0081 + docs/TQL.md. Pure value types + enums.
// Issue 0081-N: CellRenderer / ColumnSpec / BadgeRule / IconMapEntry (v1.1.0).
#pragma once
#include <string>
@@ -126,7 +127,55 @@ enum class ViewMode {
// ----------------------------------------------------------------------------
enum class JoinStrategy { Left, Inner, Right, Full };
// ----------------------------------------------------------------------------
// CellRenderer: declarative rendering mode per column (issue 0081-N, v1.1.0).
// ----------------------------------------------------------------------------
enum class CellRenderer : uint8_t {
Text = 0, // default — current behavior
Badge = 1, // colored badge per-value
Progress = 2, // progress bar (0..1 or 0..100)
Duration = 3, // milliseconds with color gradient
Icon = 4, // icon lookup by value string
// Future (Phase 2-3): Button=5, TextInput=6, Custom=7. IDs reserved.
};
// BadgeRule: maps a cell value to a colored badge label.
struct BadgeRule {
std::string value; // exact match (case-sensitive)
std::string color_hex; // "#rrggbb" or "rrggbb"
std::string label; // optional visual override; "" -> use value as-is
};
// IconMapEntry: maps a cell value to a Tabler icon macro name + optional color.
struct IconMapEntry {
std::string value;
std::string icon_name; // e.g. "TI_CHECK", "TI_X" — resolved via static map
std::string color_hex; // optional; "" -> default text color
};
// ColumnSpec: rendering spec for one column. Indexed by column position.
struct ColumnSpec {
std::string id; // stable id, used in TQL (future)
CellRenderer renderer = CellRenderer::Text;
// Badge
std::vector<BadgeRule> badges;
// Progress: cell value is float 0..1 (or 0..100 if progress_scale_100 = true)
bool progress_scale_100 = false;
std::string progress_color_hex; // override bar color; "" -> ImPlot auto
// Duration: cell value in milliseconds (float); gradient green<warn<yellow<error<red
float duration_warn_ms = 1000.0f;
float duration_error_ms = 5000.0f;
// Icon
std::vector<IconMapEntry> icon_map;
};
// ----------------------------------------------------------------------------
// Tabla extra pasada al render() para joins. Owner externo (caller).
// ----------------------------------------------------------------------------
struct TableInput {
std::string name; // identificador estable (matchea Join.source)
std::vector<std::string> headers;
@@ -134,6 +183,12 @@ struct TableInput {
const char* const* cells = nullptr; // row-major, headers.size() cols x rows filas
int rows = 0;
int cols = 0;
// NEW (issue 0081-N, v1.1.0): optional declarative renderers per column.
// empty -> all columns use default Text rendering (back-compat preserved).
// If non-empty, size must equal cols (or be 0 for "no specs").
// column_specs are caller-managed; not persisted in TQL yet (Phase 2).
std::vector<ColumnSpec> column_specs;
};
// Join clause: une la tabla actual con `source` por las parejas `on`,
File diff suppressed because it is too large Load Diff
+147
View File
@@ -0,0 +1,147 @@
---
name: data_table
kind: function
lang: cpp
domain: viz
version: "1.1.0"
purity: impure
signature: "void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, bool show_chrome = true)"
description: "Render UI completa de tabla TQL: chips bar, tabla, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI. Entry-point publica del stack data_table. Muta State segun interaccion del usuario."
tags: [tables, viz, ui, imgui, tql, cpp-tables]
uses_functions:
- compute_stage_cpp_core
- compute_pipeline_cpp_core
- compute_column_stats_cpp_core
- auto_detect_type_cpp_core
- tql_emit_cpp_core
- tql_apply_cpp_core
- tql_helpers_cpp_core
- tql_to_sql_cpp_core
- lua_engine_cpp_core
- join_tables_cpp_core
- viz_render_cpp_viz
uses_types:
- data_table_types_cpp_core
- ColumnSpec_cpp_core
- CellRenderer_cpp_core
- BadgeRule_cpp_core
- IconMapEntry_cpp_core
- TableInput_cpp_core
- State_cpp_core
- Stage_cpp_core
- StageOutput_cpp_core
- ViewMode_cpp_viz
- ViewConfig_cpp_viz
- VizPanel_cpp_viz
- Join_cpp_core
- Filter_cpp_core
- DrillStep_cpp_core
- DerivedColumn_cpp_core
- Aggregation_cpp_core
- SortClause_cpp_core
- ColumnType_cpp_core
returns: []
returns_optional: false
error_type: "error_go_core"
imports:
- imgui.h
- app_base.h
- core/data_table_types.h
- core/lua_engine.h
- core/tql_apply.h
- core/tql_emit.h
- core/tql_helpers.h
- core/tql_to_sql.h
- core/compute_stage.h
- core/compute_pipeline.h
- core/compute_column_stats.h
- core/auto_detect_type.h
- core/join_tables.h
- viz/viz_render.h
tested: true
tests:
- "back-compat: TableInput without column_specs does not crash"
- "Badge: TableInput with Badge column_spec compiles and links"
- "Progress: TableInput with Progress column_spec compiles and links"
- "Duration: TableInput with Duration column_spec compiles and links"
- "Icon: TableInput with Icon column_spec compiles and links"
test_file_path: "cpp/tests/test_column_specs.cpp"
file_path: "cpp/functions/viz/data_table.cpp"
params:
- name: id
desc: "ID unico ImGui para esta instancia, ej. '##orders_table'. Debe ser estable entre frames."
- name: tables
desc: "Lista de TableInput materializadas en memoria. tables[0] es la main por defecto; si State.main_source no-vacio se usa por nombre. Tablas extra se exponen como joinables en la UI de joins."
- name: st
desc: "Estado mutable completo: pipeline de stages, joins, viz config, ui tweaks. Debe persistir entre frames — no declarar en el stack del frame."
- name: show_chrome
desc: "Si false, oculta chips bar + breadcrumb por defecto. El usuario puede reactivar con el boton 'Show UI'. El State persiste el override del usuario entre frames."
output: "void. Muta st en respuesta a la interaccion del usuario (filtros, breakouts, sorts, drill, joins, viz mode). Los cambios son visibles en st al retornar."
---
## Ejemplo
```cpp
#include "viz/data_table.h"
#include "core/data_table_types.h"
// --- Setup (una vez) ---
data_table::TableInput t;
t.name = "orders";
t.rows = num_rows;
t.cols = num_cols;
t.cells = cells_ptr; // row-major flat array, owner externo
t.headers = {"id", "amount", "status"};
t.types = {data_table::ColumnType::Int,
data_table::ColumnType::Float,
data_table::ColumnType::String};
data_table::State st; // persiste entre frames
// --- Render (cada frame) ---
ImGui::Begin("Orders");
ImGui::BeginChild("##tbl", ImVec2(-1, -1));
data_table::render("##orders", {t}, st);
ImGui::EndChild();
ImGui::End();
```
## Cuando usarla
Cuando una app necesita tabla con filtros + agregaciones + viz + joins sobre datos en memoria. Reemplaza `ImGui::BeginTable` inline + toda la logica TQL manual. Sustituye directamente el include del playground (`tables/data_table.h`) cambiando solo el path a `viz/data_table.h`.
## Gotchas
- **ImGui + ImPlot context activos**: `render()` llama a APIs de ambas librerias. Llamar fuera de un frame activo causa UB.
- **State no stack-local**: `State` contiene el historial de drill, pipeline de stages, cache de stats y buffers de UI. Declarar en el stack del frame reset todo el estado del usuario en cada frame.
- **Drill-down propaga en State**: `st.active_stage` y `st.stages` se mutan por click en charts. El caller puede leer `st` tras `render()` para reaccionar.
- **Thread-safety**: `render()` usa `static thread_local` para buffers intermedios. Llamar solo desde el main thread de ImGui.
- **TableInput owner externo**: `cells` es un puntero raw al array del caller. Los datos deben sobrevivir durante toda la llamada a `render()`. No pasar puntero a vector que puede reallocarse.
- **Ask AI modal (llm_anthropic)**: el boton "Ask AI" usa un stub interno de `llm_anthropic` que retorna error por defecto. Para activar la feature real, compilar con `-DFN_LLM_ANTHROPIC=1` y proveer `infra/llm_anthropic.h` en el include path. Pendiente Wave 4: promover al registry.
- **FN_TQL_DUCKDB**: modo SQL del Ask AI requiere compilar con `-DFN_TQL_DUCKDB=1` y la libreria DuckDB disponible.
## Notas
No hay tests unitarios directos: `render()` requiere ImGui + ImPlot context activos (imposible sin ventana GL). Cobertura via:
1. `cpp/apps/primitives_gallery/playground/tables/` — playground original con self_test.cpp y e2e_run.sh.
2. Wave 4: migration self-tests en las apps que migren desde el playground.
**Estado Wave 3.5 (issue 0081-I):**
- Todos los includes del playground (`data_table_logic.h`, `tql.h`, `tql_to_sql.h`) eliminados. `data_table.cpp` compila sin el playground en el include path.
- `tql::apply` firma extendida ya en `tql_apply_cpp_core` (wave anterior). Resuelto.
- `tql_to_sql` promovido a `core/tql_to_sql.h`. Resuelto.
- `data_table_logic` helpers (row_to_tsv, drill, view_mode, etc.) declarados como `static` en `data_table.cpp`. No son API pública.
- `State::ensure_stage0/raw/active` implementados en `compute_stage.cpp`.
- `ColStats` struct: usa el de `compute_column_stats_cpp_core`. Unificado.
**Deuda tecnica restante (Wave 4):**
- `llm_anthropic` (Ask AI modal, issue 0080): stub interno activo. Promover a `cpp/functions/infra/llm_anthropic` para activar feature real.
- `FN_TQL_DUCKDB`: modo SQL del Ask AI sin soporte en stub. Requiere DuckDB + flag de compilacion.
- `column_specs` TQL roundtrip (Phase 2): actualmente caller-managed. No persisten en TQL emit/apply. Planificado en issue 0081-O.
## Capability growth log
v1.1.0 (2026-05-15) — declarative CellRenderer (Badge/Progress/Duration/Icon) via TableInput.column_specs sidecar. Back-compat preservado: apps existentes sin column_specs siguen funcionando sin cambios.
---
Promovido desde `cpp/apps/primitives_gallery/playground/tables/data_table.{h,cpp}` — issue 0081-H.
+111
View File
@@ -31,6 +31,13 @@ add_fn_test(test_pie_chart_math test_pie_chart_math.cpp)
add_fn_test(test_kpi_card_math test_kpi_card_math.cpp)
add_fn_test(test_bar_chart_math test_bar_chart_math.cpp)
# Issue 0081-F — auto_detect_type y compute_column_stats (extraidos del playground tables).
add_fn_test(test_auto_detect_type test_auto_detect_type.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/auto_detect_type.cpp)
add_fn_test(test_compute_column_stats test_compute_column_stats.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/auto_detect_type.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/compute_column_stats.cpp)
# Issue 0045 — tests de la logica pura extraida.
add_fn_test(test_sql_parse test_sql_parse.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/sql_parse.cpp)
@@ -136,6 +143,43 @@ else()
target_link_libraries(test_graph_icons PRIVATE OpenGL::GL)
endif()
# --- Issue 0081-B — compute_stage + compute_pipeline (TQL pure logic) -------
# tql_helpers.cpp added (issue 0081-I): compute_stage.cpp now delegates
# aggregation_alias to tql_helpers to avoid ODR conflict in fn_table_viz lib.
add_fn_test(test_compute_stage test_compute_stage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/compute_stage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_helpers.cpp)
add_fn_test(test_compute_pipeline test_compute_pipeline.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/compute_stage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/compute_pipeline.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_helpers.cpp)
# --- Issue 0081-E — join_tables: pure multi-key hash join --------------------
add_fn_test(test_join_tables test_join_tables.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/join_tables.cpp)
# --- Issue 0081-G — viz_render: dispatcher ImPlot sobre StageOutput ---------
# viz_render.cpp incluye imgui.h e implot.h y linkea contra ambas librerias.
# El test NO inicializa GL ni contexto ImGui — solo ejercita las funciones
# helper publicas (first_numeric_col, first_category_col, extract_numeric,
# extract_category) que son logica pura sobre StageOutput.
# render() requiere ImPlot context vivo: smoke real via primitives_gallery.
add_fn_test(test_viz_render test_viz_render.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/viz/viz_render.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/auto_detect_type.cpp)
target_include_directories(test_viz_render PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/imgui
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/implot)
target_link_libraries(test_viz_render PRIVATE imgui implot)
# --- lua_engine: motor Lua 5.4 sandbox (issue 0081-D) ----------------------
# lua_engine.cpp incluye lua.h/lualib.h/lauxlib.h — requiere linkar lua54.
# data_table_types.h esta en functions/core/ (ya en el include path de add_fn_test).
add_fn_test(test_lua_engine test_lua_engine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/lua_engine.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/auto_detect_type.cpp)
target_link_libraries(test_lua_engine PRIVATE lua54)
# --- layout_storage: persistencia de last_active (restore-on-open) ---------
# layout_storage.cpp incluye <imgui.h> y referencia ImGui::Save/LoadIniSettings*,
# por eso necesitamos linkar contra imgui (compilado en el target del root
@@ -145,6 +189,73 @@ add_fn_test(test_layout_storage test_layout_storage.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/layout_storage.cpp)
target_link_libraries(test_layout_storage PRIVATE SQLite::SQLite3 imgui)
# --- Issue 0081-C — tql_emit + tql_apply (TQL round-trip, pure) ------------
# tql_helpers.cpp: pure token converters (no Lua, no ImGui).
# tql_emit.cpp: State -> Lua text (no Lua runtime needed).
# tql_apply.cpp: Lua text -> State (uses Lua 5.4 C API directly, not lua_engine).
# Both tests use their own main() (no Catch2) matching fn run dispatch.
add_executable(tql_emit_test
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_emit_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_emit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_helpers.cpp)
target_include_directories(tql_emit_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../functions
${CMAKE_CURRENT_SOURCE_DIR}/../framework)
add_test(NAME tql_emit_test COMMAND tql_emit_test)
add_executable(tql_apply_test
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_apply_test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_apply.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_emit.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_helpers.cpp)
target_include_directories(tql_apply_test PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../functions
${CMAKE_CURRENT_SOURCE_DIR}/../framework)
target_link_libraries(tql_apply_test PRIVATE lua54)
add_test(NAME tql_apply_test COMMAND tql_apply_test)
# --- Issue 0081-I — fn_table_viz static lib smoke test ---------------------
# Linker test: verifies that all 9 registry .cpp files in fn_table_viz resolve
# symbols correctly when linked as a static lib. Does NOT call data_table::render
# (requires ImGui context + playground headers). Uses its own main().
if(TARGET fn_table_viz)
add_executable(test_fn_table_viz_smoke
${CMAKE_CURRENT_SOURCE_DIR}/test_fn_table_viz_smoke.cpp)
target_include_directories(test_fn_table_viz_smoke PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../functions
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/imgui
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/implot)
target_link_libraries(test_fn_table_viz_smoke PRIVATE fn_table_viz)
add_test(NAME test_fn_table_viz_smoke COMMAND test_fn_table_viz_smoke)
endif()
# --- Issue 0081-N — declarative CellRenderer (Badge/Progress/Duration/Icon) --
# Smoke + back-compat tests for TableInput.column_specs (v1.1.0).
# Verifies type construction + link resolution; does NOT call render() (ImGui).
if(TARGET fn_table_viz)
add_executable(test_column_specs
${CMAKE_CURRENT_SOURCE_DIR}/test_column_specs.cpp)
target_include_directories(test_column_specs PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/../functions
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/imgui
${CMAKE_CURRENT_SOURCE_DIR}/../vendor/implot)
target_link_libraries(test_column_specs PRIVATE fn_table_viz)
add_test(NAME test_column_specs COMMAND test_column_specs)
endif()
# --- Issue 0081 — tql_to_sql: pure TQL State -> SQL DuckDB emitter ----------
# Pure logic: no DuckDB linked, no ImGui. Only data_table_types.h + tql_helpers.
add_fn_test(test_tql_to_sql test_tql_to_sql.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_to_sql.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/tql_helpers.cpp)
# --- Issue 0081 — llm_anthropic: Anthropic API client pure helpers ----------
# Only tests pure functions (build_request_body, extract_code_block,
# parse_response_text) + call_api via FN_LLM_MOCK_RESPONSE injection.
# Real HTTP roundtrip requires a valid API key (manual_test).
add_fn_test(test_llm_anthropic test_llm_anthropic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/../functions/core/llm_anthropic.cpp)
# --- Visual golden-image diff (issue 0048) ---------------------------------
# El binario primitives_gallery se compila con --capture; el test compara los
# PNGs generados con los goldens en cpp/tests/golden/. Si no hay goldens o el
+196
View File
@@ -0,0 +1,196 @@
// test_column_specs.cpp — Smoke / back-compat tests for declarative cell renderers.
// Issue 0081-N, v1.1.0.
//
// These tests verify:
// 1. TableInput without column_specs compiles and links (back-compat).
// 2-5. TableInput with Badge/Progress/Duration/Icon column_specs compiles and links.
//
// None of these tests call data_table::render() (requires ImGui context).
// They only verify that the new types are usable and that the symbols from
// fn_table_viz link correctly.
//
// Build: cmake --build cpp/build/linux --target test_column_specs
// Run: ./cpp/build/linux/tests/test_column_specs
#include "core/data_table_types.h"
#include "viz/data_table.h"
#include <cassert>
#include <cstdio>
#include <string>
#include <vector>
using namespace data_table;
// Shared trivial dataset (3 rows x 4 cols).
static const char* g_cells[] = {
"ok", "0.75", "250", "fn",
"error", "0.20", "3500", "type",
"warn", "1.00", "12000", "fn",
};
static const std::vector<std::string> g_headers = {"status", "progress", "duration_ms", "kind"};
static const std::vector<ColumnType> g_types = {
ColumnType::String, ColumnType::Float, ColumnType::Float, ColumnType::String
};
// ---------------------------------------------------------------------------
// Test 1: back-compat — TableInput without column_specs.
// ---------------------------------------------------------------------------
static void test_no_column_specs() {
TableInput t;
t.name = "t1";
t.rows = 3;
t.cols = 4;
t.cells = g_cells;
t.headers = g_headers;
t.types = g_types;
// column_specs intentionally left empty (back-compat: default behavior).
assert(t.column_specs.empty() && "column_specs must be empty by default");
// Verify that render symbol is still linkable (no ImGui context needed
// to take the address; the linker verifies the symbol resolves).
auto* render_fn = &data_table::render;
(void)render_fn;
std::printf("PASS: test_no_column_specs (back-compat, column_specs empty)\n");
}
// ---------------------------------------------------------------------------
// Test 2: Badge renderer — construct TableInput with Badge column_spec.
// ---------------------------------------------------------------------------
static void test_badge_column_spec() {
TableInput t;
t.name = "t2";
t.rows = 3;
t.cols = 4;
t.cells = g_cells;
t.headers = g_headers;
t.types = g_types;
// Column 0: Badge renderer mapping ok/error/warn to colors.
ColumnSpec cs_status;
cs_status.id = "status";
cs_status.renderer = CellRenderer::Badge;
cs_status.badges = {
BadgeRule{"ok", "#22c55e", "OK"},
BadgeRule{"error", "#ef4444", ""}, // label empty -> use value
BadgeRule{"warn", "#f59e0b", "WARN"},
};
// Remaining columns: default Text.
t.column_specs.resize(4); // default-initialized = CellRenderer::Text
t.column_specs[0] = cs_status;
assert(t.column_specs.size() == 4);
assert(t.column_specs[0].renderer == CellRenderer::Badge);
assert(t.column_specs[0].badges.size() == 3);
assert(t.column_specs[1].renderer == CellRenderer::Text);
std::printf("PASS: test_badge_column_spec (3 badge rules, remaining cols Text)\n");
}
// ---------------------------------------------------------------------------
// Test 3: Progress renderer.
// ---------------------------------------------------------------------------
static void test_progress_column_spec() {
TableInput t;
t.name = "t3";
t.rows = 3;
t.cols = 4;
t.cells = g_cells;
t.headers = g_headers;
t.types = g_types;
ColumnSpec cs_progress;
cs_progress.id = "progress";
cs_progress.renderer = CellRenderer::Progress;
cs_progress.progress_scale_100 = false;
cs_progress.progress_color_hex = "#3b82f6";
t.column_specs.resize(4);
t.column_specs[1] = cs_progress;
assert(t.column_specs[1].renderer == CellRenderer::Progress);
assert(!t.column_specs[1].progress_scale_100);
assert(t.column_specs[1].progress_color_hex == "#3b82f6");
std::printf("PASS: test_progress_column_spec\n");
}
// ---------------------------------------------------------------------------
// Test 4: Duration renderer.
// ---------------------------------------------------------------------------
static void test_duration_column_spec() {
TableInput t;
t.name = "t4";
t.rows = 3;
t.cols = 4;
t.cells = g_cells;
t.headers = g_headers;
t.types = g_types;
ColumnSpec cs_dur;
cs_dur.id = "duration_ms";
cs_dur.renderer = CellRenderer::Duration;
cs_dur.duration_warn_ms = 500.0f;
cs_dur.duration_error_ms = 2000.0f;
t.column_specs.resize(4);
t.column_specs[2] = cs_dur;
assert(t.column_specs[2].renderer == CellRenderer::Duration);
assert(t.column_specs[2].duration_warn_ms == 500.0f);
assert(t.column_specs[2].duration_error_ms == 2000.0f);
std::printf("PASS: test_duration_column_spec (warn=500ms error=2000ms)\n");
}
// ---------------------------------------------------------------------------
// Test 5: Icon renderer.
// ---------------------------------------------------------------------------
static void test_icon_column_spec() {
TableInput t;
t.name = "t5";
t.rows = 3;
t.cols = 4;
t.cells = g_cells;
t.headers = g_headers;
t.types = g_types;
ColumnSpec cs_icon;
cs_icon.id = "kind";
cs_icon.renderer = CellRenderer::Icon;
cs_icon.icon_map = {
IconMapEntry{"fn", "TI_BOLT", "#3b82f6"},
IconMapEntry{"type", "TI_DATABASE", ""},
};
t.column_specs.resize(4);
t.column_specs[3] = cs_icon;
assert(t.column_specs[3].renderer == CellRenderer::Icon);
assert(t.column_specs[3].icon_map.size() == 2);
assert(t.column_specs[3].icon_map[0].value == "fn");
assert(t.column_specs[3].icon_map[0].icon_name == "TI_BOLT");
// Verify render symbol still links with column_specs populated.
auto* render_fn = &data_table::render;
(void)render_fn;
std::printf("PASS: test_icon_column_spec (2 entries, render symbol links)\n");
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main() {
std::printf("=== test_column_specs ===\n");
test_no_column_specs();
test_badge_column_spec();
test_progress_column_spec();
test_duration_column_spec();
test_icon_column_spec();
std::printf("=== ALL TESTS PASSED (5/5) ===\n");
return 0;
}
+12 -5
View File
@@ -14,16 +14,23 @@ Indice de grupos de capacidades del registry. Cada grupo agrupa >=3 funciones qu
| Grupo | N | Que cubre |
|---|---|---|
| [bigquery](bigquery.md) | 26 | _(editar — promovido automaticamente)_ |
| [nlp](nlp.md) | 33 | _(editar — promovido automaticamente)_ |
| [docker](docker.md) | 38 | _(editar — promovido automaticamente)_ |
| [android](android.md) | 37 | _(editar — promovido automaticamente)_ |
| [metabase](metabase.md) | 106 | _(editar — promovido automaticamente)_ |
| [registry](registry.md) | 17 | Auditoria y monitorizacion del propio registry: copied-code, uses-functions, unused, proposals, telemetria |
| [systemd](systemd.md) | 14 | Generar, instalar, restart y status de unit files systemd via SSH (deploys a VPS) |
| [ssh](ssh.md) | 19 | Operar hosts remotos via SSH: config, conn, ejecutar comandos, port-forward, deploys con SCP/rsync |
| [deploy](deploy.md) | 21 | Deploy completo Go/C++ a VPS o Windows: Docker+Traefik, systemd, rsync, health checks |
| [mantine](mantine.md) | 63 | Frontend Mantine v9 + @fn_library: theming, layout, formularios, modales, instalacion |
| [bigquery](bigquery.md) | 26 | Operar Google BigQuery via SDK Python: queries, dataset/table CRUD, jobs, schema, exports |
| [nlp](nlp.md) | 33 | Extraccion NLP: PDFs, OCR, chunking, GLiNER/GLiREL, dedup, agregacion de entities/relations |
| [docker](docker.md) | 38 | Operar Docker desde Go/Bash: build/run/stop, compose, networks, volumes, logs, deploys |
| [android](android.md) | 37 | Toolbelt Android desde WSL2: adb, emuladores AVD, APK build/install, Capacitor, logcat |
| [metabase](metabase.md) | 106 | Operar Metabase via API REST: auth, cards, dashboards, collections, snippets, permissions |
| [doctor](doctor.md) | 11 | Diagnostico read-only del registry: artefactos, servicios, drift, funciones huerfanas |
| [notebook](notebook.md) | 5 | Operar Jupyter Lab colaborativo (discover/read/exec/write/kernel) |
| [cpp-windows](cpp-windows.md) | 7 | Compilar, desplegar, lanzar y verificar apps C++ en Windows desde WSL2 |
| [git](git.md) | 19 | Operaciones git y Gitea: clonar, commit, push/pull, hooks, TBD, webhooks, sync entre PCs |
| [playwright](playwright.md) | 6 | E2E browser: launch chromium, login kanban, drag dnd-kit, keyboard sequence, wait predicate, assert class |
| [cpp-tables](tql.md) | 9 | Table Query Language C++ puro: filter, group, agg, sort, join, stats, formulas Lua, round-trip emit/apply |
| [data-table-renderers](data_table_renderers.md) | 1 | API declarativa de cell renderers para data_table: Badge, Progress, Duration, Icon via TableInput.column_specs |
## Como anadir grupo
+127
View File
@@ -0,0 +1,127 @@
# data_table_renderers — declarative cell renderers (v1.1.0)
Tag: `cpp-tables` (mismo grupo que TQL; los renderers son parte del stack `data_table`).
Extiende `data_table_cpp_viz` con una API declarativa para renderizar columnas con
Badge, Progress, Duration e Icon **sin escribir ImGui inline**. Activado via el
campo opcional `column_specs` de `TableInput`. Back-compat 100%: apps sin
`column_specs` no necesitan cambios.
## Tipos nuevos en `data_table_types.h`
| Tipo | Que es |
|---|---|
| `CellRenderer` | enum class: `Text=0`, `Badge=1`, `Progress=2`, `Duration=3`, `Icon=4` |
| `BadgeRule` | value (exact match) + color_hex + label opcional |
| `IconMapEntry` | value + icon_name (ej. `"TI_BOLT"`) + color_hex opcional |
| `ColumnSpec` | id + renderer + badges / progress fields / duration thresholds / icon_map |
| `TableInput::column_specs` | `std::vector<ColumnSpec>` sidecar opcional (size 0 o == cols) |
## Ejemplo canonico: Recent Executions (status Badge + duration Duration)
```cpp
#include "viz/data_table.h"
#include "core/data_table_types.h"
// --- Datos (owner externo) ---
static const char* g_exec_cells[] = {
"ok", "142",
"error", "3850",
"ok", "72",
"warn", "1100",
};
// --- Setup (una vez, en init de la app o antes del loop) ---
data_table::TableInput t;
t.name = "executions";
t.rows = 4;
t.cols = 2;
t.cells = g_exec_cells;
t.headers = {"status", "duration_ms"};
t.types = {data_table::ColumnType::String, data_table::ColumnType::Float};
// Columna 0: Badge por valor de status
data_table::ColumnSpec cs_status;
cs_status.id = "status";
cs_status.renderer = data_table::CellRenderer::Badge;
cs_status.badges = {
data_table::BadgeRule{"ok", "#22c55e", "OK"},
data_table::BadgeRule{"error", "#ef4444", "ERROR"},
data_table::BadgeRule{"warn", "#f59e0b", "WARN"},
};
// Columna 1: Duration con gradiente verde/amarillo/rojo
data_table::ColumnSpec cs_dur;
cs_dur.id = "duration_ms";
cs_dur.renderer = data_table::CellRenderer::Duration;
cs_dur.duration_warn_ms = 500.0f;
cs_dur.duration_error_ms = 2000.0f;
t.column_specs = {cs_status, cs_dur};
data_table::State st; // persiste entre frames
// --- Render loop ---
ImGui::Begin("Executions");
ImGui::BeginChild("##exec_tbl", ImVec2(-1, -1));
data_table::render("##exec", {t}, st);
ImGui::EndChild();
ImGui::End();
```
## Ejemplo: Progress bar + Icon
```cpp
data_table::ColumnSpec cs_progress;
cs_progress.id = "completion";
cs_progress.renderer = data_table::CellRenderer::Progress;
cs_progress.progress_scale_100 = true; // cell value es 0..100
cs_progress.progress_color_hex = "#3b82f6"; // azul; "" -> ImPlot auto
data_table::ColumnSpec cs_icon;
cs_icon.id = "kind";
cs_icon.renderer = data_table::CellRenderer::Icon;
cs_icon.icon_map = {
data_table::IconMapEntry{"fn", "TI_BOLT", "#3b82f6"},
data_table::IconMapEntry{"type", "TI_DATABASE", ""},
data_table::IconMapEntry{"app", "TI_SETTINGS", "#6b7280"},
};
t.column_specs = {cs_progress, cs_icon};
```
## Iconos soportados en CellRenderer::Icon
El lookup es una tabla estatica de ~30 nombres frecuentes:
```
TI_CHECK TI_X TI_ALERT_CIRCLE TI_CIRCLE_DOT
TI_CLOCK TI_LOADER TI_BAN TI_PLAYER_PLAY
TI_PLAYER_PAUSE TI_PLAYER_STOP TI_DATABASE TI_SETTINGS
TI_USER TI_USERS TI_FILE TI_FOLDER
TI_REFRESH TI_BOLT TI_INFO_CIRCLE TI_ARROW_UP
TI_ARROW_DOWN TI_ARROW_RIGHT TI_ARROW_LEFT TI_DOTS
TI_EYE TI_EYE_OFF TI_EDIT TI_TRASH
TI_COPY TI_EXTERNAL_LINK
```
Si el `icon_name` no esta en la tabla, la celda se renderiza como texto plano.
## Fronteras
- **Solo Column 0..N posicional**: `column_specs[i]` aplica a la columna en posicion `i` del `TableInput` original. No se mapea por nombre (Phase 2).
- **No persiste en TQL**: `column_specs` son responsabilidad del caller — se construyen cada frame o en el setup. `tql_emit`/`tql_apply` no los tocan (Phase 2 planificado).
- **No implementa Button/TextInput/Custom** (Phase 2-3 separados).
- **Stage N (agregado)**: los renderers se aplican por posicion de columna del output agregado — si el breakout cambia el numero de columnas, revisar los indices.
## Gotchas
- `column_specs.size()` debe ser 0 (sin specs) o igual a `t.cols`. Mezcla de tamaños puede causar out-of-bounds silencioso (el render hace `c < column_specs.size()` guard, pero es mejor ser expliciito).
- `hex_to_imcolor` acepta `"#rrggbb"` o `"rrggbb"`. Alpha siempre 1.0. Sin soporte para `rgba`.
- El ColorRule existente de State (`st.color_rules`) sigue funcionando — ambos sistemas coexisten. Si hay conflicto, `column_specs` toma prioridad para el contenido de la celda; `color_rules` pinta el fondo via `TableSetBgColor`.
- En el renderer Badge el `Selectable` con background coloreado consume el item para hover/click — la seleccion de rango con drag puede verse afectada visualmente en columnas Badge.
## Notas
- Tests: `cpp/tests/test_column_specs.cpp` (5 tests: 1 back-compat + 4 renderer types). Smoke/linker; no requieren ImGui context.
- TQL roundtrip pendiente: issue 0081-O (Phase 2).
+22
View File
@@ -0,0 +1,22 @@
---
name: BadgeRule
lang: cpp
domain: core
version: "1.0.0"
algebraic: product
definition: |
struct BadgeRule {
std::string value;
std::string color_hex;
std::string label;
};
description: "Regla de badge para CellRenderer::Badge. Mapea un valor exacto de celda a un color hex y un label visual opcional. Si label vacio, se usa value. Issue 0081-N."
tags: [tables, tql, types, cpp-tables]
uses_types: []
file_path: "cpp/functions/core/data_table_types.h"
---
## Notas
`color_hex` acepta `"#rrggbb"` o `"rrggbb"`. Sin alpha — siempre opaco.
`value` es exact match case-sensitive. Sin wildcards ni regex en Phase 1.
+24
View File
@@ -0,0 +1,24 @@
---
name: CellRenderer
lang: cpp
domain: core
version: "1.0.0"
algebraic: sum
definition: |
enum class CellRenderer : uint8_t {
Text = 0,
Badge = 1,
Progress = 2,
Duration = 3,
Icon = 4,
};
description: "Enum declarativo de modo de render por columna para data_table. Text = comportamiento actual (back-compat). Badge/Progress/Duration/Icon activan renderizado visual via TableInput.column_specs. Issue 0081-N."
tags: [tables, tql, types, cpp-tables]
uses_types: []
file_path: "cpp/functions/core/data_table_types.h"
---
## Notas
Sum type (enum). Valores futuros reservados: Button=5, TextInput=6, Custom=7.
El renderer se usa via `ColumnSpec.renderer` dentro de `TableInput.column_specs`.
+32
View File
@@ -0,0 +1,32 @@
---
name: ColumnSpec
lang: cpp
domain: core
version: "1.0.0"
algebraic: product
definition: |
struct ColumnSpec {
std::string id;
CellRenderer renderer = CellRenderer::Text;
std::vector<BadgeRule> badges;
bool progress_scale_100 = false;
std::string progress_color_hex;
float duration_warn_ms = 1000.0f;
float duration_error_ms = 5000.0f;
std::vector<IconMapEntry> icon_map;
};
description: "Spec declarativa de render para una columna de data_table. Indexada por posicion en TableInput.column_specs. Activa Badge/Progress/Duration/Icon sin escribir ImGui inline. Issue 0081-N."
tags: [tables, tql, types, cpp-tables]
uses_types:
- CellRenderer_cpp_core
- BadgeRule_cpp_core
- IconMapEntry_cpp_core
file_path: "cpp/functions/core/data_table_types.h"
---
## Notas
`id` es un identificador estable para uso futuro en TQL roundtrip (Phase 2).
En Phase 1 no se serializa — el caller construye `column_specs` cada frame.
Los campos de renderer no activo se ignoran: si `renderer=Badge`, solo se leen
`badges`; si `renderer=Duration`, solo `duration_warn_ms` y `duration_error_ms`.
+24
View File
@@ -0,0 +1,24 @@
---
name: IconMapEntry
lang: cpp
domain: core
version: "1.0.0"
algebraic: product
definition: |
struct IconMapEntry {
std::string value;
std::string icon_name;
std::string color_hex;
};
description: "Entrada de mapa de iconos para CellRenderer::Icon. Mapea un valor de celda a un nombre de icono Tabler (ej. 'TI_BOLT') y un color opcional. Issue 0081-N."
tags: [tables, tql, types, cpp-tables]
uses_types: []
file_path: "cpp/functions/core/data_table_types.h"
---
## Notas
`icon_name` debe coincidir con un macro de `cpp/functions/core/icons_tabler.h`.
Lookup estatico en `data_table.cpp` cubre ~30 iconos frecuentes. Si el nombre
no esta en la tabla, la celda se renderiza como texto plano.
`color_hex` vacio -> color de texto por defecto.