docs(flows): DoD obligatorio con user-facing surface + abrir issues 0100-0103 (taxonomia, frontmatter migration, dev_console, work dashboard)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-17 00:07:03 +02:00
parent a03675113a
commit 6ad82167bb
72 changed files with 3920 additions and 303 deletions
+36
View File
@@ -1,5 +1,7 @@
#include "core/app_about.h"
#include "app_base.h"
#include "app_modules.h"
#include "imgui.h"
#include <string>
@@ -58,6 +60,40 @@ void about_window_render() {
ImGui::TextWrapped("%s", g_description.c_str());
}
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
// --- Framework version (issue 0097) ---
ImGui::Text("Framework");
ImGui::SameLine();
ImGui::TextDisabled("v%s", fn::framework_version());
// --- Modules consumidos por la app (issue 0097) ---
if (fn::app_modules_count > 0) {
ImGui::Spacing();
ImGui::Text("Modules (%lu)", fn::app_modules_count);
if (ImGui::BeginTable("##fn_modules_table", 2,
ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerH |
ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthFixed, 140.0f);
ImGui::TableSetupColumn("Version", ImGuiTableColumnFlags_WidthFixed, 80.0f);
for (unsigned long i = 0; i < fn::app_modules_count; ++i) {
const auto& m = fn::app_modules_array[i];
if (!m.name) continue;
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(0);
ImGui::TextUnformatted(m.name);
if (m.description && *m.description && ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s", m.description);
}
ImGui::TableSetColumnIndex(1);
ImGui::TextDisabled("v%s", m.version ? m.version : "?");
}
ImGui::EndTable();
}
}
ImGui::Spacing();
ImGui::Separator();
ImGui::TextDisabled("fn_registry");
+46 -7
View File
@@ -2,6 +2,7 @@
// 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).
// v1.4.0: ChipRule / ColorStop / CategoricalChip / ColorScale renderers.
#pragma once
#include <string>
@@ -131,16 +132,19 @@ enum class JoinStrategy { Left, Inner, Right, Full };
// CellRenderer: declarative rendering mode per column (issue 0081-N, v1.1.0).
// Phase 2 (issue 0081-O, v1.2.0): Button=5 added.
// Phase 2.5 (issue 0081-O.5, v1.3.0): Dots=8 added (inline status timeline).
// v1.4.0: CategoricalChip=9, ColorScale=10.
// ----------------------------------------------------------------------------
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
Button = 5, // clickable button; emits TableEvent::ButtonClick
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
Button = 5, // clickable button; emits TableEvent::ButtonClick
// 6, 7: reserved for Phase 3 (TextInput, Custom).
Dots = 8, // inline dots sparkline; cell = separator-delimited tokens
Dots = 8, // inline dots sparkline; cell = separator-delimited tokens
CategoricalChip = 9, // filled circle (8px) to left of text; color by value match
ColorScale = 10, // continuous N-color gradient tint on cell background
};
// ----------------------------------------------------------------------------
@@ -178,6 +182,19 @@ struct IconMapEntry {
std::string color_hex; // optional; "" -> default text color
};
// ChipRule: maps a cell value to a dot color for CategoricalChip renderer (v1.4.0).
// If no rule matches, no dot is drawn (fallback: plain text only).
struct ChipRule {
std::string match; // exact match (case-sensitive) against cell value
std::string color; // "#rrggbb" hex color for the filled circle
};
// ColorStop: one stop in an N-color gradient for ColorScale renderer (v1.4.0).
struct ColorStop {
float position; // 0.0 (leftmost/min) to 1.0 (rightmost/max)
std::string color; // "#rrggbb" hex color at this stop
};
// ColumnSpec: rendering spec for one column. Indexed by column position.
struct ColumnSpec {
std::string id; // stable id, used in TQL
@@ -215,6 +232,20 @@ struct ColumnSpec {
float dots_glyph_size = 0.0f; // glyph size px; 0 = default font size
int dots_max = 0; // hard limit on dots shown; 0 = no limit
bool dots_show_count = false; // if true, appends " (N)" after dots
// CategoricalChip (v1.4.0): CellRenderer::CategoricalChip.
// Draws a filled circle (radius ~4px) to the left of the cell text.
// Color is determined by matching cell value against `chips` rules.
// Always visible (not hover-only). If no rule matches, no dot is drawn.
std::vector<ChipRule> chips; // value → color rules
// ColorScale (v1.4.0): CellRenderer::ColorScale.
// Maps numeric cell value to a background tint via N-color gradient LERP.
// Low alpha so text remains legible.
double range_min = 0.0; // value at t=0.0
double range_max = 1.0; // value at t=1.0
float range_alpha = 0.25f; // [0..1]; background tint opacity
std::vector<ColorStop> range_stops; // N≥2 stops; empty → default green→amber→red
};
// ----------------------------------------------------------------------------
@@ -292,6 +323,14 @@ struct State {
// Caller-provided column_specs take precedence over aux_column_specs.
std::vector<std::vector<ColumnSpec>> aux_column_specs;
// Per-table "Show UI" toggle. Moved from global UiCache to per-State so each
// table's chrome (chips bar) can be toggled independently (issue: multiple
// tables on screen, "Show UI" used to flip all at once).
// Defaults: user_set=true + visible=false => chrome closed by default, ignoring
// the API arg show_chrome from frame 1 (preserves legacy behavior).
bool chrome_user_set = true;
bool chrome_user_visible = false;
// Helpers (definidos en compute_stage.cpp).
Stage& raw();
const Stage& raw() const;
+59 -6
View File
@@ -540,12 +540,14 @@ ApplyResult apply(const std::string& lua_text,
lua_getfield(L, -1, "renderer");
if (lua_isstring(L, -1)) {
std::string rn = lua_tostring(L, -1);
if (rn == "badge") cs.renderer = data_table::CellRenderer::Badge;
else if (rn == "progress") cs.renderer = data_table::CellRenderer::Progress;
else if (rn == "duration") cs.renderer = data_table::CellRenderer::Duration;
else if (rn == "icon") cs.renderer = data_table::CellRenderer::Icon;
else if (rn == "button") cs.renderer = data_table::CellRenderer::Button;
else if (rn == "dots") cs.renderer = data_table::CellRenderer::Dots;
if (rn == "badge") cs.renderer = data_table::CellRenderer::Badge;
else if (rn == "progress") cs.renderer = data_table::CellRenderer::Progress;
else if (rn == "duration") cs.renderer = data_table::CellRenderer::Duration;
else if (rn == "icon") cs.renderer = data_table::CellRenderer::Icon;
else if (rn == "button") cs.renderer = data_table::CellRenderer::Button;
else if (rn == "dots") cs.renderer = data_table::CellRenderer::Dots;
else if (rn == "categorical_chip") cs.renderer = data_table::CellRenderer::CategoricalChip;
else if (rn == "color_scale") cs.renderer = data_table::CellRenderer::ColorScale;
else cs.renderer = data_table::CellRenderer::Text;
}
lua_pop(L, 1);
@@ -642,6 +644,57 @@ ApplyResult apply(const std::string& lua_text,
if (lua_isnumber(L, -1)) cs.dots_glyph_size = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
// CategoricalChip (v1.4.0)
lua_getfield(L, -1, "chips");
if (lua_istable(L, -1)) {
int nc = (int)lua_rawlen(L, -1);
for (int j = 1; j <= nc; ++j) {
lua_rawgeti(L, -1, j);
if (lua_istable(L, -1)) {
data_table::ChipRule cr;
lua_getfield(L, -1, "match");
if (lua_isstring(L, -1)) cr.match = lua_tostring(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "color");
if (lua_isstring(L, -1)) cr.color = lua_tostring(L, -1);
lua_pop(L, 1);
cs.chips.push_back(std::move(cr));
}
lua_pop(L, 1);
}
}
lua_pop(L, 1); // chips
// ColorScale (v1.4.0)
lua_getfield(L, -1, "range_min");
if (lua_isnumber(L, -1)) cs.range_min = (double)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "range_max");
if (lua_isnumber(L, -1)) cs.range_max = (double)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "range_alpha");
if (lua_isnumber(L, -1)) cs.range_alpha = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "range_stops");
if (lua_istable(L, -1)) {
int ns = (int)lua_rawlen(L, -1);
for (int j = 1; j <= ns; ++j) {
lua_rawgeti(L, -1, j);
if (lua_istable(L, -1)) {
data_table::ColorStop stop;
lua_getfield(L, -1, "position");
if (lua_isnumber(L, -1)) stop.position = (float)lua_tonumber(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "color");
if (lua_isstring(L, -1)) stop.color = lua_tostring(L, -1);
lua_pop(L, 1);
cs.range_stops.push_back(std::move(stop));
}
lua_pop(L, 1);
}
}
lua_pop(L, 1); // range_stops
// Tooltip
lua_getfield(L, -1, "tooltip");
if (lua_isstring(L, -1)) cs.tooltip = lua_tostring(L, -1);
+44 -8
View File
@@ -283,8 +283,7 @@ std::string emit(const State& state,
// Emit the block only if at least one spec has a non-default renderer OR tooltip.
bool any_renderable = false;
for (const auto& cs : specs) {
if (cs.renderer != data_table::CellRenderer::Text || cs.tooltip_on_hover ||
cs.renderer == data_table::CellRenderer::Dots) {
if (cs.renderer != data_table::CellRenderer::Text || cs.tooltip_on_hover) {
any_renderable = true; break;
}
}
@@ -297,12 +296,14 @@ std::string emit(const State& state,
// renderer
const char* rname = "text";
switch (cs.renderer) {
case data_table::CellRenderer::Badge: rname = "badge"; break;
case data_table::CellRenderer::Progress: rname = "progress"; break;
case data_table::CellRenderer::Duration: rname = "duration"; break;
case data_table::CellRenderer::Icon: rname = "icon"; break;
case data_table::CellRenderer::Button: rname = "button"; break;
case data_table::CellRenderer::Dots: rname = "dots"; break;
case data_table::CellRenderer::Badge: rname = "badge"; break;
case data_table::CellRenderer::Progress: rname = "progress"; break;
case data_table::CellRenderer::Duration: rname = "duration"; break;
case data_table::CellRenderer::Icon: rname = "icon"; break;
case data_table::CellRenderer::Button: rname = "button"; break;
case data_table::CellRenderer::Dots: rname = "dots"; break;
case data_table::CellRenderer::CategoricalChip: rname = "categorical_chip"; break;
case data_table::CellRenderer::ColorScale: rname = "color_scale"; break;
default: break;
}
out += ", renderer = " + lua_string_literal(rname);
@@ -370,6 +371,41 @@ std::string emit(const State& state,
out += std::string(", dots_glyph_size = ") + buf;
}
}
// CategoricalChip (v1.4.0)
if (cs.renderer == data_table::CellRenderer::CategoricalChip) {
if (!cs.chips.empty()) {
out += ", chips = {\n";
for (const auto& cr : cs.chips) {
out += " { match = " + lua_string_literal(cr.match);
out += ", color = " + lua_string_literal(cr.color);
out += " },\n";
}
out += " }";
}
}
// ColorScale (v1.4.0)
if (cs.renderer == data_table::CellRenderer::ColorScale) {
char buf[64];
std::snprintf(buf, sizeof(buf), "%g", cs.range_min);
out += std::string(", range_min = ") + buf;
std::snprintf(buf, sizeof(buf), "%g", cs.range_max);
out += std::string(", range_max = ") + buf;
if (cs.range_alpha != 0.25f) {
std::snprintf(buf, sizeof(buf), "%g", (double)cs.range_alpha);
out += std::string(", range_alpha = ") + buf;
}
if (!cs.range_stops.empty()) {
out += ", range_stops = {\n";
for (const auto& stop : cs.range_stops) {
std::snprintf(buf, sizeof(buf), "%g", (double)stop.position);
out += " { position = ";
out += buf;
out += ", color = " + lua_string_literal(stop.color);
out += " },\n";
}
out += " }";
}
}
// Tooltip
if (cs.tooltip_on_hover) {
out += ", tooltip = " + lua_string_literal(cs.tooltip.empty() ? "auto" : cs.tooltip);
File diff suppressed because it is too large Load Diff
-58
View File
@@ -1,58 +0,0 @@
#pragma once
// data_table — render UI completa de tabla TQL.
// Entry-point publica del stack data_table del registry.
// Issue 0081-H. Promovido desde cpp/apps/primitives_gallery/playground/tables/data_table.h
// Phase 2 (issue 0081-O, v1.2.0): Button renderer + event sink + tooltip + RightClick.
//
// Uso basico (back-compat, sin events):
// data_table::State st; // persistir entre frames
// ImGui::Begin("Window"); ImGui::BeginChild("tbl", {-1,-1});
// data_table::render("my_table", {table1, table2}, st);
// ImGui::EndChild(); ImGui::End();
//
// Uso con events (Phase 2):
// std::vector<data_table::TableEvent> events;
// data_table::render("my_table", {table1, table2}, st, &events);
// for (auto& ev : events) {
// if (ev.kind == data_table::TableEventKind::ButtonClick &&
// ev.action_id == "cancel") { ... }
// }
//
// Requiere ImGui context + ImPlot context activos.
// Namespace identico al playground para facilitar migracion (solo cambiar include path).
#include "core/data_table_types.h"
#include <vector>
namespace data_table {
// render — Render barra-de-chips + tabla + panels de visualizacion.
// Mutates `st` en respuesta a la interaccion del usuario.
//
// `id` — ID unico de ImGui para esta instancia (ej. "##my_table").
// `tables` — lista de TableInput. 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.
// `st` — estado mutable. Debe persistir entre frames (no stack-local).
// `events_out` — if non-null, populated with UI events (ButtonClick,
// RowDoubleClick, RowRightClick) fired this frame. The caller
// clears/reads the vector after each render call.
// Pass nullptr to disable event collection (back-compat).
// `show_chrome` — si false, oculta la barra de chips + breadcrumb por defecto.
// El usuario puede reactivarla via el boton "Show UI".
void render(const char* id,
const std::vector<TableInput>& tables,
State& st,
std::vector<TableEvent>* events_out,
bool show_chrome = true);
// Overload for back-compat: same as render(..., nullptr, show_chrome).
inline void render(const char* id,
const std::vector<TableInput>& tables,
State& st,
bool show_chrome = true)
{
render(id, tables, st, nullptr, show_chrome);
}
} // namespace data_table
-200
View File
@@ -1,200 +0,0 @@
---
name: data_table
kind: function
lang: cpp
domain: viz
version: "1.3.6"
purity: impure
signature: "void data_table::render(const char* id, const std::vector<TableInput>& tables, State& st, std::vector<TableEvent>* events_out = nullptr, 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, 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). 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
- TableEvent_cpp_core
- TableEventKind_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"
- "Button: TableEvent struct constructible; render() with events_out links"
- "Tooltip: ColumnSpec with tooltip_on_hover=true compiles and links"
- "Back-compat: both render() signatures (with/without events_out) link"
- "Dots: ColumnSpec with CellRenderer::Dots + badges constructs correctly"
- "Dots TQL roundtrip: State::aux_column_specs accepts Dots spec"
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, aux_column_specs (Phase 2). Debe persistir entre frames — no declarar en el stack del frame."
- name: events_out
desc: "Puntero a vector de TableEvent. Si non-null, se puebla con eventos de este frame (ButtonClick, RowDoubleClick, RowRightClick). El caller limpia/lee el vector despues de cada render. Pasar nullptr para desactivar (back-compat)."
- 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. Events emitted via events_out."
---
## 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", "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`.
## 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.
- **events_out no se limpia**: `render()` solo hace `push_back`. El caller debe llamar `events.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 detectar `RowRightClick`.
- **aux_column_specs merge**: si `TableInput.column_specs` esta vacio pero `State.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_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.
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.
---
Promovido desde `cpp/apps/primitives_gallery/playground/tables/data_table.{h,cpp}` — issue 0081-H.