Files
fn_registry/dev/issues/completed/0108-tables-playground-aggressive-testbed.md
egutierrez b9716a7cd6 chore: snapshot WIP previo + flow 0008 + 7 sub-issues (0112-0119)
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>
2026-05-18 18:17:08 +02:00

357 lines
20 KiB
Markdown

---
id: "0108"
title: "App tables_qa — testbed agresivo del modulo data_table + deprecar tables_playground"
status: in-progress
type: app
domain:
- cpp-stack
- apps-infra
- meta
scope: app
priority: alta
depends:
- "0107"
blocks: []
related:
- "0081"
- "0097"
created: 2026-05-17
updated: 2026-05-17
tags: [data-table, modules, testbed, qa, regression, version-selector, apphub]
---
# 0108 — tables_playground como testbed agresivo del modulo data_table
## Problema
`apps/primitives_gallery/playground/tables/` hoy es el playground original de tablas — fue el origen de `modules/data_table`. Pero esta DESACOPLADO del modulo: linkea sus PROPIAS copias de `data_table.cpp`, `data_table_logic.cpp`, `tql.cpp`, `tql_to_sql.cpp`, `lua_engine.cpp`, `llm_anthropic.cpp`, `viz.cpp` (versiones legacy). El `self_test.cpp` (430 checks) prueba la logica legacy, no el modulo.
Consecuencias:
- Bug en el modulo no se detecta en el playground.
- Cualquier mejora del modulo (post-0107) NO se valida contra el playground hasta que algun app lo encuentre en runtime.
- El playground es deuda — codigo duplicado que nadie mantiene a la par.
Ademas, las apps consumidoras (`app_gestion`, `data_factory`, `registry_dashboard`, `services_monitor`, etc.) usan el modulo con patrones repetidos pero no documentados como "casos de uso canonicos":
- `CellRenderer::CategoricalChip` con `chips` map (status, kind, enabled).
- `CellRenderer::ColorScale` con `range_min`/`range_max`/`range_alpha` (duracion en ms).
- `CellRenderer::Badge` con `badges` map (version pinning, status, env).
- `CellRenderer::Duration` con `duration_warn_ms` / `duration_error_ms`.
- `CellRenderer::Button` con `button_action` → consumido en `events_out` (`TableEventKind::ButtonClick`).
- `TableEventKind::RowDoubleClick` → abrir detalle / drilldown.
- Joins entre `TableInput[0]` (main) y `TableInput[i]` (joinables) con `JoinStrategy`.
- Color rules condicionales (numericas + categoricas) — declaradas en `State.stages[k].color_rules`.
NO HAY un sitio donde un agente o humano pueda ver TODOS estos patrones funcionando lado-a-lado para verificar visualmente que el modulo se comporta bien. El primitives_gallery hace eso para componentes basicos, pero no para tablas avanzadas.
## Decision (actualizada 2026-05-17)
Crear **app NUEVA `tables_qa`** (no evolucionar `tables_playground` in-place). Razon: playground tiene 8 .cpp legacy + self_test 2921 LOC mezclado. Mas limpio crear desde 0 con `init_cpp_app` + `uses_modules: [data_table_cpp]` desde el principio.
`tables_playground` se **deprecara** tras `tables_qa` verde:
- `apps/primitives_gallery/playground/tables/` → mover a `apps/primitives_gallery/playground/tables_legacy_archive/` (gitignored o conservado por historia).
- Entry en `cpp/CMakeLists.txt` que registra `tables_playground` se elimina.
- `tables_playground_self_test` deja de buildear.
### Identidad de la app
- `name`: `tables_qa`
- `description`: "Testbed agresivo del modulo data_table — multi-tabla, menu QA toggleable, inyector de eventos, counters live, version selector + downgrade side-by-side."
- `icon.phosphor`: `"test-tube"` (Phosphor — claro QA, sin ambiguedad)
- `icon.accent`: `"#f59e0b"` (amber — testing zone, distinto de los colores existentes de otras apps)
- `dir_path`: `apps/tables_qa`
- `repo_url`: `https://gitea.organic-machine.com/dataforge/tables_qa`
- `tags`: `[imgui, dashboard, qa, testing, data-table, regression]`
### Registro en App Hub
Tras build verde:
1. `./fn run generate_app_icon "test-tube" "#f59e0b" "apps/tables_qa/appicon.ico"` — genera .ico multi-res.
2. Anadir trio obligatorio (`description` + `icon.phosphor` + `icon.accent`) al `app.md` (ya cubierto en frontmatter del scaffolder).
3. `./fn run refresh_app_hub` — regenera icons PNG + manifest TSV del hub + reinicia `app_hub_launcher` si esta corriendo.
4. Verificar tarjeta visible en hub con icono amber + descripcion.
## Decision (original — refundar a aplicacion completa)
Refundar `tables_playground` como **testbed agresivo del modulo `fn_module_data_table`**:
1. **Migrar al modulo**: el playground PASA a depender de `fn_module_data_table` static lib. Eliminar las copias locales (`data_table.cpp`, `data_table_logic.cpp`, `tql.cpp`, `lua_engine.cpp`, `viz.cpp`, `tql_to_sql.cpp`, `llm_anthropic.cpp`). El playground se convierte en cliente del modulo igual que las apps reales.
2. **Multi-tabla**: 6-8 tablas demo cubriendo cada `CellRenderer` + cada `TableEventKind` + joins + color rules. Cada una con su `State` persistente. Tab navegable (`ImGui::BeginTabBar`).
3. **Menu de acciones replicando apps**: barra superior con toggles que activan/desactivan features tipicas usadas por apps:
- "Buttons en celdas (Cancel/Retry/Inspect)" → emite ButtonClick.
- "Color condicional numerico (ColorScale en duraciones)".
- "Color condicional categorico (CategoricalChip por status)".
- "Badge version pinning".
- "Duration con thresholds (warn=1000ms / error=5000ms)".
- "Dots sparkline (status timeline)".
- "Icon map (TI_CHECK/TI_X/TI_CIRCLE)".
- "Join Left/Inner/Right/Full entre tablas".
- "Row double-click → modal de detalle".
- "Row right-click → context menu".
- "TQL pipeline (filter/breakout/agg/sort) con chips".
- "Ask AI panel (TQL natural language)".
- "Save/Load TQL desde disco".
- "Export CSV".
- "Drill-down entre stages".
4. **Version selector + downgrade**:
- Mostrar version actual del modulo en header (`fn::framework_version()` / `data_table::module_version()` — anadir API).
- Selector de "modo compatibilidad" para simular comportamiento de versiones anteriores (`v1.4`, `v1.3`, etc.). Implementacion: feature flags por version dentro del modulo o branch-by-config al construir `TableInput`. Util para auditar regresiones cuando bumpemos versiones.
- Side-by-side: renderizar la misma `TableInput` con la version actual a la izquierda y con el modo compatibilidad a la derecha. Diff visual inmediato.
5. **Capture & compare visual**: integrar con el sistema `primitives_gallery --capture` existente (golden images) para que cada commit corra screenshots del testbed y compare contra master. Si pixel diff > threshold → CI rojo.
6. **Self-test del modulo**: nuevo `tables_playground_module_test.cpp` que ejerza la API publica `data_table::render()` con casos sinteticos. Headless (sin GLFW window) usando `imgui_test_engine` (si FN_BUILD_TESTS=ON). Cubre:
- Cada `CellRenderer` enum se renderiza sin crash.
- `events_out` recibe el evento correcto al simular click.
- State se mantiene entre frames.
- TQL pipeline produce output esperado.
- Joins respetan strategy.
## Arquitectura
### Estructura final del playground
```
apps/primitives_gallery/playground/tables/
CMakeLists.txt # linka fn_module_data_table; ELIMINA sources legacy
main.cpp # entry: fn::run_app + render_playground()
playground.cpp # render_playground(): tab bar + acciones menu
tables/ # NEW — 1 archivo por tabla demo
tbl_basic.cpp # Text + numerico, base case
tbl_renderers.cpp # 1 columna por cada CellRenderer (visual review)
tbl_buttons.cpp # Cancel/Retry/Inspect — emite ButtonClick
tbl_color_rules.cpp # ColorScale numerico + CategoricalChip
tbl_durations.cpp # Duration renderer + thresholds
tbl_dots.cpp # Dots sparkline (status timelines)
tbl_joins.cpp # 2 tablas + Left/Inner/Right/Full
tbl_tql.cpp # Pipeline TQL completo + Ask AI
tbl_drill.cpp # Drill-down stages
version_compat.cpp # version selector + side-by-side downgrade
test_module.cpp # NEW: self_test contra API publica del modulo
e2e_run.sh # actualizar: build + test_module + capture
(ELIMINAR: data_table.cpp, data_table_logic.cpp, tql.cpp, tql_to_sql.cpp,
lua_engine.cpp, llm_anthropic.cpp, viz.cpp, tql_duckdb.cpp, self_test.cpp)
README.md # NEW: documenta cada tabla demo + checklist e2e
```
### API publica del modulo expuesta al playground (documentar)
```cpp
// Entry function
namespace data_table {
void render(const char* id,
const std::vector<TableInput>& tables,
State& state,
std::vector<TableEvent>* events_out = nullptr,
bool show_chrome = true);
}
// Tipos publicos consumidos por las apps
struct TableInput {
std::string name;
std::vector<std::string> headers;
std::vector<ColumnType> types;
const char* const* cells; // row-major, rows*cols
int rows;
int cols;
std::vector<ColumnSpec> column_specs; // renderer config per col
};
struct ColumnSpec {
std::string id;
CellRenderer renderer = CellRenderer::Text;
// Badge / CategoricalChip / Dots
std::vector<BadgeRule> badges;
std::vector<ChipRule> chips;
// Progress
bool progress_scale_100 = false;
std::string progress_color_hex;
// Duration
float duration_warn_ms = 1000.0f;
float duration_error_ms = 5000.0f;
// Icon
std::vector<IconMapEntry> icon_map;
// Button
std::string button_action;
std::string button_label;
std::string button_color_hex;
// ColorScale
double range_min = 0.0;
double range_max = 1.0;
float range_alpha = 0.25f;
std::vector<ColorStop> color_stops;
// Tooltip
std::string tooltip;
bool tooltip_on_hover = false;
};
enum class CellRenderer : uint8_t {
Text=0, Badge=1, Progress=2, Duration=3, Icon=4, Button=5,
Dots=8, CategoricalChip=9, ColorScale=10,
};
enum class TableEventKind : uint8_t {
ButtonClick=1, RowDoubleClick=2, RowRightClick=3, CellEdit=4,
};
struct TableEvent {
TableEventKind kind;
int row;
int col;
std::string column_id;
std::string action_id; // ColumnSpec::button_action on ButtonClick
std::string value;
};
struct State {
// (opaca al consumidor — gestionada internamente; debe persistir entre frames)
};
// Module metadata (NEW en este issue)
namespace data_table {
const char* module_version(); // ej. "2.0.0"
const char* module_description();
}
```
Esta especificacion va a `docs/MODULES_API.md` (issue 0107f) como el contrato canonico de `data_table_cpp`.
### Patron de uso canonico (apps)
```cpp
static data_table::State st;
data_table::TableInput tbl;
tbl.name = "runs";
tbl.headers = {"id", "status", "duration_ms", "started_at"};
tbl.types = {ColumnType::String, ColumnType::String, ColumnType::Float, ColumnType::Date};
tbl.cells = &cells_flat[0];
tbl.rows = N;
tbl.cols = 4;
// Configure renderers
tbl.column_specs.resize(tbl.cols);
for (int i = 0; i < tbl.cols; i++) tbl.column_specs[i].id = tbl.headers[i];
tbl.column_specs[1].renderer = data_table::CellRenderer::CategoricalChip;
tbl.column_specs[1].chips = { {"ok", "#22c55e"}, {"error", "#ef4444"}, {"running", "#f59e0b"} };
tbl.column_specs[2].renderer = data_table::CellRenderer::ColorScale;
tbl.column_specs[2].range_min = 0.0;
tbl.column_specs[2].range_max = 5000.0;
tbl.column_specs[2].range_alpha = 0.30f;
// Button column for retry action
data_table::ColumnSpec retry_col;
retry_col.id = "actions";
retry_col.renderer = data_table::CellRenderer::Button;
retry_col.button_action = "retry_run";
retry_col.button_label = "Retry";
tbl.headers.push_back("actions");
tbl.column_specs.push_back(retry_col);
// Render + consume events
std::vector<data_table::TableEvent> events;
data_table::render("##runs_tbl", {tbl}, st, &events);
for (auto& ev : events) {
if (ev.kind == data_table::TableEventKind::ButtonClick && ev.action_id == "retry_run") {
// app handler
}
if (ev.kind == data_table::TableEventKind::RowDoubleClick) {
// open detail modal
}
}
```
## Tareas
- [ ] **1.1** Investigar TODOS los patrones de uso del modulo en apps (script audit):
```bash
grep -rhnE "data_table::(render|TableInput|State|ColumnSpec|TableEvent|CellRenderer::|TableEventKind::|JoinStrategy)" \
apps/*/main.cpp apps/*/*.cpp projects/*/apps/*/*.cpp 2>/dev/null | sort -u > /tmp/data_table_api_usage.txt
```
Producir tabla de patrones canonicos en `docs/MODULES_API.md`.
- [ ] **2.1** Backup `apps/primitives_gallery/playground/tables/{data_table.cpp,data_table_logic.cpp,tql.cpp,tql_to_sql.cpp,lua_engine.cpp,llm_anthropic.cpp,viz.cpp,tql_duckdb.cpp,self_test.cpp}` a `apps/primitives_gallery/playground/tables/legacy_archive/` (gitignored o conservado por historia).
- [ ] **2.2** Editar `apps/primitives_gallery/playground/tables/CMakeLists.txt`:
- Quitar sources legacy.
- `target_link_libraries(tables_playground PRIVATE fn_module_data_table)`.
- El target `tables_playground_self_test` se renombra a `tables_playground_module_test` con sources nuevos.
- [ ] **3.1** Crear `playground.cpp` con tab bar + menu acciones.
- [ ] **3.2** Crear 9 archivos `tables/tbl_*.cpp` con cada caso demo.
- [ ] **3.3** Crear `version_compat.cpp` con selector + side-by-side.
- [ ] **3.4** Anadir API `data_table::module_version()` + `module_description()` a `modules/data_table/data_table.h`. Implementacion lee `version_generated.h` (post 0107c bump a 2.0.0).
- [ ] **4.1** Crear `test_module.cpp` headless con `imgui_test_engine` (gated por `FN_BUILD_TESTS=ON`):
- 1 test por `CellRenderer` enum.
- 1 test por `TableEventKind`.
- 1 test de joins.
- 1 test de TQL pipeline.
- Compara cells output via golden TSV.
- [ ] **4.2** Migrar lo aplicable del `self_test.cpp` legacy (430 checks) a tests del modulo. Lo que prueba logica pura ya extraida al registry (parse_number, compare, tql parsing, lua) va a `cpp/functions/core/*_test.cpp`. Lo que prueba render() va a `test_module.cpp`.
- [ ] **5.1** Actualizar `e2e_run.sh`: build + module_test + capture screenshot.
- [ ] **5.2** Integrar con `primitives_gallery --capture` (golden images de cada tab del testbed).
- [ ] **5.3** README.md con checklist de QA por tab.
- [ ] **6.1** Verificar que las apps consumidoras (7) siguen compilando y comportandose igual.
- [ ] **6.2** `fn index` para que el playground actualizado quede registrado (aunque sea playground, su `app.md` puede declarar `uses_modules: [data_table_cpp]` con min_version 2.0.0).
## Decisiones de diseño
1. **Eliminar sources legacy**: opcion alternativa era mantenerlos como golden-reference. Decision: eliminarlos. Razon: si quedan vivos, la tentacion de "arreglar el playground" sin tocar el modulo permanece. Forzar a usar el modulo cierra el loop.
2. **Side-by-side downgrade**: opcion alternativa era branchear el modulo en `data_table_v1` static lib paralelo. Decision: feature flags por version dentro del modulo actual (`v_compat_mode`). Menos duplicacion, mas pragmatico.
3. **`module_version()` como API publica**: alternativa era leer `version_generated.h` desde la app. Decision: API publica + estable. Las apps que quieran mostrar la version del modulo en su About panel lo hacen via la funcion, no via include privado.
## Prerequisitos
- Issue 0107 (estandarizacion modulos) cerrado y `modules-v2` activo. **Bloqueo duro**: sin 0107, el sistema sigue con drift y refactorizar el playground encima de codigo inestable es desperdicio.
## Riesgos
- **Headless test con imgui_test_engine**: requiere `FN_BUILD_TESTS=ON`. Si no esta disponible en CI, los tests no corren. Mitigacion: documentar requirement en CI config + fallback a tests de logica pura sin UI.
- **Version compat mode** es facil de hacer mal: el codigo del modulo se llena de `if (v_compat < X)`. Mitigacion: limite estricto — solo se mantiene compat para las 2 versiones previas (`v_current - 1`, `v_current - 2`). Mas alla, se elimina y se documenta breaking change.
- **Capture and compare** depende de fonts/DPI/driver GL → false positives en CI. Mitigacion: capture solo en Linux x86_64 con driver mesa fijado.
## Infra de testing reutilizable (existente en el repo)
El testbed se construye combinando estos mecanismos ya disponibles. Ningun nuevo motor.
| Mecanismo | Ubicacion | Que aporta al testbed |
|---|---|---|
| Catch2 unit | `cpp/tests/` (50+ tests, macro `add_fn_test`) | Tests de logica pura de sub-funciones del registry usadas por el modulo (`compute_column_stats`, `tql_emit`, `lua_engine`, `join_tables`, `tql_to_sql`). Ya existen — los tomamos como dado. |
| Golden PNG diff | `cpp/tests/golden/` (43 PNGs) + `png_diff.cpp` | Cada tab del testbed exporta golden via `primitives_gallery --capture`. Diff en CI bloquea drift visual. |
| Auto-driving worker | `apps/altsnap_jitter_test/main.cpp` (modelo de referencia) | Pattern: worker thread fakea eventos + counters monotonicos en `fn::internal::*` + `set_force_alt_for_test()` bypass. Replicar para `data_table`: counters `fn::internal::data_table::{button_click_count, row_double_click_count, color_rule_applied_count, ...}` + worker que inyecta clicks. |
| Dear ImGui Test Engine | `cpp/framework/app_base.h:205-225` (`fn::run_app_test`, gated por `FN_BUILD_TESTS=ON`) | UI-level tests: `IM_REGISTER_TEST` lambdas que llaman `ctx->ItemClick`, `ctx->ItemDoubleClick`, verifican events emitidos. **CERO consumidores actuales** — el testbed sera el primer cliente. |
| `--self-test` flag | `.claude/rules/e2e_validation.md` (patron canonico) | `tables_playground --self-test` corre toda la suite headless, exit 0/1. Compatible con CI sin display. |
| Cross-platform e2e | `bash/functions/infra/e2e_run_cpp_windows.sh` | Funcion bash: cross-compile mingw + deploy Desktop + taskkill + run native + capturar stdout/exit. Linux CI valida Windows. |
### Panel de control QA agresivo (UI del testbed)
Barra superior con:
1. **Toggles por feature** (15+ checkboxes). Cada toggle muta `ColumnSpec` o `State` en runtime, mismo patron que apps reales hacen en codigo de setup. El usuario QA puede activar/desactivar y ver cambio visual inmediato sin recompilar.
2. **Inyector de eventos**: 4 botones — "Simulate ButtonClick", "Simulate RowDoubleClick", "Simulate RowRightClick", "Simulate Drill". Cada uno llama el worker thread pattern de altsnap → verifica que `TableEvent` apropiado se emite.
3. **Counters live**: contador por evento, contador por renderer aplicado, latency p50/p95 por frame (medir desde `ImGui::GetIO().Framerate`). Visible siempre, util para detectar regresiones de performance.
4. **Version selector**: dropdown con versiones del modulo (`2.0.0` actual, `1.4.0` compat, `1.3.0` compat...). Cambio re-renderiza la misma data side-by-side: actual vs version seleccionada. Diff visual inmediato + diff de events emitidos.
5. **Boton "Run --self-test"**: ejecuta toda la suite headless dentro de la misma ventana. Output en log panel. Util para iteracion rapida sin relanzar.
6. **Boton "Export golden"**: corre `--capture` sobre cada tab, escribe PNGs a `golden/data_table_testbed/`. Para regenerar baseline tras cambio visual intencional.
### Counters internos a anadir al modulo
Issue 0108 anade en `modules/data_table/data_table.cpp`:
```cpp
namespace data_table::internal {
int button_click_count();
int row_double_click_count();
int row_right_click_count();
int color_rule_applied_count();
int tql_stages_executed();
int last_render_duration_us();
void reset_counters();
}
```
Mismo modelo que `fn::internal::*` para altsnap. Test-only observability, cost cero en prod (counters atomicos triviales).
## Notas
- Issue 0108 NO empieza hasta 0107 cerrar. Bloqueado duro.
- Se referencia desde `docs/MODULES_API.md` (0107f) como el ejemplo canonico de "como usar el modulo data_table".
- Cuando 0108 cierre, abrir issue 0109 paralelo para `chat_ia` (que era el siguiente modulo planeado al inicio de 0107).