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

20 KiB

id, title, status, type, domain, scope, priority, depends, blocks, related, created, updated, tags
id title status type domain scope priority depends blocks related created updated tags
0108 App tables_qa — testbed agresivo del modulo data_table + deprecar tables_playground in-progress app
cpp-stack
apps-infra
meta
app alta
0107
0081
0097
2026-05-17 2026-05-17
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)

// 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)

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):
    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:

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).