--- 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& tables, State& state, std::vector* events_out = nullptr, bool show_chrome = true); } // Tipos publicos consumidos por las apps struct TableInput { std::string name; std::vector headers; std::vector types; const char* const* cells; // row-major, rows*cols int rows; int cols; std::vector column_specs; // renderer config per col }; struct ColumnSpec { std::string id; CellRenderer renderer = CellRenderer::Text; // Badge / CategoricalChip / Dots std::vector badges; std::vector 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 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 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 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).