Snapshot de WIP acumulado de sesiones previas antes de merge wave 1 del flow 0008 (kanban_cpp + agent_runner_api + DoD schema). Incluye: - dev/flows/0008-kanban-cpp-and-agent-workflows.md - dev/issues/0112-0119*.md (7 sub-issues) - WIP previo en cmd/fn/doctor.go, registry/*, modules/, cpp/, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
18 KiB
name, kind, lang, domain, version, purity, signature, description, tags, uses_functions, uses_types, returns, returns_optional, error_type, imports, tested, tests, test_file_path, file_path, params, output
| name | kind | lang | domain | version | purity | signature | description | tags | uses_functions | uses_types | returns | returns_optional | error_type | imports | tested | tests | test_file_path | file_path | params | output | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| data_table | function | cpp | viz | 1.5.0 | impure | void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, std::vector<TableEvent>* events_out = nullptr, bool show_chrome = true) | Render UI completa de tabla TQL: chips bar, tabla, viz panels, column-stats inline, drill, color rules, joins, TQL editor, Ask AI, Button renderer, event sink (ButtonClick/RowDoubleClick/RowRightClick), tooltip per-cell, column_specs persisted in TQL. Dots renderer para sparkline-like de status (v1.3.0). CategoricalChip + ColorScale en v1.4.0. v1.5.0: stats/seleccion/drill-history/row-inspector pasan a vivir en State (por tabla, antes eran globales — multiples tablas en pantalla compartian estado). Conditional color extendido con tres modos: CellBg (legacy), CategoricalDot (autopalette por valor) y NumericRange (gradiente N-color). Hover del menu contextual de header restaurado. Entry-point publica del stack data_table. Muta State segun interaccion del usuario. |
|
|
|
false | error_go_core |
|
true |
|
cpp/tests/test_column_specs.cpp | modules/data_table/data_table.cpp |
|
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. Events emitted via events_out. |
Ejemplo
#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", "actions"};
t.types = {data_table::ColumnType::Int,
data_table::ColumnType::Float,
data_table::ColumnType::String,
data_table::ColumnType::String};
// Phase 2: declarative renderers + tooltip
t.column_specs.resize(4);
t.column_specs[2].renderer = data_table::CellRenderer::Badge;
t.column_specs[2].badges = {{"paid","#22c55e","Paid"},{"pending","#f59e0b",""}};
t.column_specs[2].tooltip = "auto";
t.column_specs[2].tooltip_on_hover = true;
t.column_specs[3].renderer = data_table::CellRenderer::Button;
t.column_specs[3].button_action = "cancel_order";
t.column_specs[3].button_label = "Cancel";
data_table::State st; // persiste entre frames
std::vector<data_table::TableEvent> events;
// --- Render (cada frame) ---
ImGui::Begin("Orders");
ImGui::BeginChild("##tbl", ImVec2(-1, -1));
events.clear();
data_table::render("##orders", {t}, st, &events);
ImGui::EndChild();
ImGui::End();
// --- Process events ---
for (const auto& ev : events) {
if (ev.kind == data_table::TableEventKind::ButtonClick &&
ev.action_id == "cancel_order") {
cancel_order(ev.row); // app handles the action
}
if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
open_order_detail(ev.row);
}
}
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.
Usar CategoricalChip cuando quieras un indicador visual (dot de color) siempre visible a la izquierda del texto de la celda, para columnas categoricas (estado, tipo, severidad). Mas discreto que Badge y sin hover-only. Usar ColorScale cuando la columna sea numerica continua y quieras dar contexto visual de "alto/bajo/medio" con un fondo tintado proporcional al valor — util para latencias, scores, porcentajes, metricas.
Per-table state (v1.5.0)
Antes de v1.5.0 los siguientes campos vivian en un singleton UiState interno y se aplicaban a TODAS las tablas a la vez. Desde v1.5.0 viven en State (parametro st), por tabla:
| Campo | Significado |
|---|---|
stats_mode, stats_cache, stats_last_* |
Toggle "Show stats" + cache de stats por col. |
sel_anchor_row/col, sel_end_row/col, sel_active, sel_dragging |
Seleccion rectangular drag para Ctrl+C (TSV). |
inspect_row, inspect_open |
Modal "Inspect row..." del menu contextual. |
drill_back, drill_forward |
Historial de drill back/forward. |
Estado modal (un solo popup abierto a la vez en toda la app) sigue en el singleton interno: edit-chip popups, addfilter/addbreakout/addagg/addsort, Ask AI, Custom column formula, TQL show/apply, drill popup, header context. Estos NO necesitan separacion por tabla porque ImGui solo permite un popup abierto a la vez.
Conditional color rules (v1.5.0)
State::color_rules (tipo std::vector<ColorRule>) acepta tres modos via ColorRule::kind (enum ColorRuleKind):
| Modo | Cuando usar | Campos relevantes |
|---|---|---|
CellBg (legacy) |
Resaltar bg de celda completa cuando valor == equals. Un color por regla, multiples reglas por col. |
equals, color |
CategoricalDot |
Categoricas con muchos valores distintos. Dibuja un dot circular a la izquierda del texto. Color autoasignado por hash (palette de 12 vibrant Tailwind-500) o por mapeo explicito dot_map. |
dot_alpha, dot_radius_px, dot_map |
NumericRange |
Numericas continuas. Gradiente N-color sobre bg de celda. Valores fuera de [range_min, range_max] saturan en los stops extremos. |
range_min, range_max, range_alpha, range_stops |
Menu UI
Right-click cabecera → Conditional color → [Cell bg / Categorical dot / Numeric range]. La auto-deteccion preselecciona NumericRange para columnas Int/Float, CellBg para el resto. Cada Apply REEMPLAZA la regla existente del mismo kind en esa columna (puedes combinar las tres en la misma columna). "Clear col" borra todas las reglas de la columna.
Ejemplo programatico
data_table::State st;
// Cell bg cuando status == "error" -> rojo.
data_table::ColorRule r1;
r1.col = 3;
r1.kind = data_table::ColorRuleKind::CellBg;
r1.equals = "error";
r1.color = IM_COL32(220, 60, 60, 120);
st.color_rules.push_back(r1);
// Dot autoasignado por categoria de `region`.
data_table::ColorRule r2;
r2.col = 1;
r2.kind = data_table::ColorRuleKind::CategoricalDot;
r2.dot_alpha = 1.0f;
r2.dot_radius_px = 4.0f;
// (dot_map vacio -> autopalette por hash. Para mapeo fijo:)
// r2.dot_map = {{"EU","#3b82f6"},{"NA","#22c55e"},{"AS","#f59e0b"}};
st.color_rules.push_back(r2);
// Gradient verde→ambar→rojo sobre latency_ms en [0, 500].
data_table::ColorRule r3;
r3.col = 5;
r3.kind = data_table::ColorRuleKind::NumericRange;
r3.range_min = 0.0;
r3.range_max = 500.0;
r3.range_alpha = 0.25f;
r3.range_stops = {
{0.0f, "#22c55e"},
{0.5f, "#f59e0b"},
{1.0f, "#ef4444"},
};
st.color_rules.push_back(r3);
CategoricalDot vs CategoricalChip renderer
Hay dos rutas para dots categoricos. Cuando elegir cada una:
| Mecanismo | Donde se define | Cuando |
|---|---|---|
ColorRule::CategoricalDot (v1.5.0) |
State::color_rules (ad-hoc por usuario via menu) |
Exploracion interactiva: el usuario decide en runtime si pintar dots y con que palette. Autopalette por hash sin definir mapeo. |
CellRenderer::CategoricalChip (v1.4.0) |
TableInput::column_specs[c] (declarativo por el caller) |
App quiere dots SIEMPRE para una columna especifica con mapeo explicito chips: [{match, color}]. |
Ambos son compatibles — pueden coexistir sin colision.
Gotchas
- ImGui + ImPlot context activos:
render()llama a APIs de ambas librerias. Llamar fuera de un frame activo causa UB. - State no stack-local:
Statecontiene 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_stageyst.stagesse mutan por click en charts. El caller puede leersttrasrender()para reaccionar. - Thread-safety:
render()usastatic thread_localpara buffers intermedios. Llamar solo desde el main thread de ImGui. - TableInput owner externo:
cellses un puntero raw al array del caller. Los datos deben sobrevivir durante toda la llamada arender(). No pasar puntero a vector que puede reallocarse. - events_out no se limpia:
render()solo hacepush_back. El caller debe llamarevents.clear()antes de cada frame o acumulara eventos de frames anteriores. - Button + celda vacia: si el cell value es vacio, el boton NO se dibuja. La app controla cuando mostrar el boton poniendo un value no vacio (ej. "1" o el ID de la fila).
- RowRightClick emite evento Y abre popup interno: la tabla de stages (stage>0) sigue abriendo su popup de drill. En el raw table (stage 0), se emite el evento pero el popup de drill antiguo tambien puede abrirse via
U.open_cell_popup. El caller puede ignorar el popup interno y gestionar su propio menu al detectarRowRightClick. - CategoricalChip sin regla coincidente → sin dot: si ninguna
ChipRule.matchcoincide con el valor de la celda, solo se renderiza el texto. Definir una regla de fallback explicita si se necesita dot para valores no mapeados. - ColorScale clampa fuera de rango: valores por debajo de
range_minse tratan como t=0 (primer stop) y valores por encima derange_maxcomo t=1 (ultimo stop). Definirrange_min/range_maxsensatos para que el gradiente sea informativo; valores muy alejados de la mayoria hacen que todo el gradiente aparezca en un extremo. - aux_column_specs merge: si
TableInput.column_specsesta vacio peroState.aux_column_specs[0]no,render()los aplica automaticamente. Si el caller pasa column_specs no vacios, ganan sobre los del State. - Ask AI modal (llm_anthropic): el boton "Ask AI" usa un stub interno de
llm_anthropicque retorna error por defecto. Para activar la feature real, compilar con-DFN_LLM_ANTHROPIC=1y proveerinfra/llm_anthropic.hen el include path. Pendiente Wave 4: promover al registry. - FN_TQL_DUCKDB: modo SQL del Ask AI requiere compilar con
-DFN_TQL_DUCKDB=1y la libreria DuckDB disponible.
Notas
No hay tests unitarios directos: render() requiere ImGui + ImPlot context activos (imposible sin ventana GL). Cobertura via:
cpp/apps/primitives_gallery/playground/tables/— playground original con self_test.cpp y e2e_run.sh.- 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.cppcompila sin el playground en el include path. tql::applyfirma extendida ya entql_apply_cpp_core(wave anterior). Resuelto.tql_to_sqlpromovido acore/tql_to_sql.h. Resuelto.data_table_logichelpers (row_to_tsv, drill, view_mode, etc.) declarados comostaticendata_table.cpp. No son API pública.State::ensure_stage0/raw/activeimplementados encompute_stage.cpp.ColStatsstruct: usa el decompute_column_stats_cpp_core. Unificado.
Deuda tecnica restante (Wave 4):
llm_anthropic(Ask AI modal, issue 0080): stub interno activo. Promover acpp/functions/infra/llm_anthropicpara activar feature real.FN_TQL_DUCKDB: modo SQL del Ask AI sin soporte en stub. Requiere DuckDB + flag de compilacion.column_specsTQL 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.
v1.2.0 (2026-05-15) — Button renderer + event sink (ButtonClick/RowDoubleClick/RowRightClick) + tooltip per cell + column_specs persisted in TQL (aux_column_specs roundtrip). Back-compat preserved: events_out=nullptr by default; existing render() callers unchanged.
v1.3.0 (2026-05-15) — Dots renderer for inline status timelines (sparkline-like). Reuses badges for color mapping. dots_max/dots_separator/dots_show_count/dots_glyph_size fields. TQL roundtrip. dag_engine_ui canonical use case (10-col antipattern -> 6-col fix).
v1.3.1 (2026-05-15) — Dots renderer now draws filled circles via ImDrawList instead of Unicode glyph. Font-independent: works regardless of TTF glyph coverage. Closes "dots show as ?" bug in dag_engine_ui.
v1.3.2 (2026-05-15) — Hover dimming: row uses muted alpha (0.05 vs default 0.31); hovered cell gets a subtle overlay (~9% white) via ImDrawList. Badge no longer SpanAllColumns. Closes "table-wide bright highlight on hover".
v1.3.3 (2026-05-15) — Selectable bg disabled for Text/empty cells (was duplicating with manual overlay → gray double-hover). Explicit ImVec2 size on Selectable so empty cells get a hit area (fixes drag-select skipping empties). Single uniform hover layer across all cell renderers.
v1.3.4 (2026-05-15) — Row height tightened: GetTextLineHeight() (no spacing) replaces GetTextLineHeightWithSpacing() in Selectable size + manual overlay rect. Removes inflated row vertical padding introduced in v1.3.3.
v1.3.5 (2026-05-15) — Cell hover paints via TableSetBgColor (covers entire cell bg including CellPadding) instead of manual AddRectFilled inside content area. Hit-test expanded by CellPadding for proper edge-to-edge coverage. Closes "hover has gap between cell borders".
v1.3.6 (2026-05-15) — Selection (drag-range) also paints via TableSetBgColor — same edge-to-edge coverage as hover. Header/HeaderHovered/HeaderActive colors set to fully transparent so Selectable doesn't paint anything; all cell bg states (hover, selected, selected+hover) go through TableSetBgColor uniformly.
v1.4.0 (2026-05-16) — new renderers: CategoricalChip (dot izquierda + text, always visible, replaces hover-only color-on-text for categorical) + ColorScale (continuous N-color LERP gradient for numeric cells, configurable range_min/range_max/range_stops/range_alpha). New types: ChipRule{match,color} + ColorStop{position,color} in data_table_types.h. TQL roundtrip (emit+apply) for both renderers. 4 headless tests added to test_column_specs.cpp.
v1.5.0 (2026-05-17) — per-table state isolation: stats_mode, stats_cache, stats_last_*, sel_*, inspect_*, drill_back/forward movidos de singleton UiState interno a State. Antes: toggle "Show stats" / drag-select / Inspect row se aplicaban a TODAS las tablas visibles a la vez. Ahora cada tabla los lleva. Conditional color extendido: nuevo enum ColorRuleKind { CellBg, CategoricalDot, NumericRange } + campos en ColorRule (dot_alpha, dot_radius_px, dot_map, range_min/max/alpha/stops). Menu del header autodetecta el modo por tipo de columna (Int/Float → NumericRange; resto → CellBg). CategoricalDot autoasigna colores desde palette de 12 hash-deterministico — no hace falta mapear cada valor a mano. Fix: hover del menu contextual del header invisible cuando las table-styles pisaban HeaderHovered a transparente — restaurado dentro del scope del popup. Breaking en data_table_types.h: ColStop + DrillStep movidos arriba para que State pueda contenerlos por valor; consumidores que redefinian ColStats localmente (playground data_table_logic.h) eliminados — usar el del registry (compute_column_stats.h, ya transitivamente incluido).
Promovido desde cpp/apps/primitives_gallery/playground/tables/data_table.{h,cpp} — issue 0081-H.