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>
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 |
|
app | alta |
|
|
2026-05-17 | 2026-05-17 |
|
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::CategoricalChipconchipsmap (status, kind, enabled).CellRenderer::ColorScaleconrange_min/range_max/range_alpha(duracion en ms).CellRenderer::Badgeconbadgesmap (version pinning, status, env).CellRenderer::Durationconduration_warn_ms/duration_error_ms.CellRenderer::Buttonconbutton_action→ consumido enevents_out(TableEventKind::ButtonClick).TableEventKind::RowDoubleClick→ abrir detalle / drilldown.- Joins entre
TableInput[0](main) yTableInput[i](joinables) conJoinStrategy. - 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 aapps/primitives_gallery/playground/tables_legacy_archive/(gitignored o conservado por historia).- Entry en
cpp/CMakeLists.txtque registratables_playgroundse elimina. tables_playground_self_testdeja de buildear.
Identidad de la app
name:tables_qadescription: "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_qarepo_url:https://gitea.organic-machine.com/dataforge/tables_qatags:[imgui, dashboard, qa, testing, data-table, regression]
Registro en App Hub
Tras build verde:
./fn run generate_app_icon "test-tube" "#f59e0b" "apps/tables_qa/appicon.ico"— genera .ico multi-res.- Anadir trio obligatorio (
description+icon.phosphor+icon.accent) alapp.md(ya cubierto en frontmatter del scaffolder). ./fn run refresh_app_hub— regenera icons PNG + manifest TSV del hub + reiniciaapp_hub_launchersi esta corriendo.- 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:
- Migrar al modulo: el playground PASA a depender de
fn_module_data_tablestatic 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. - Multi-tabla: 6-8 tablas demo cubriendo cada
CellRenderer+ cadaTableEventKind+ joins + color rules. Cada una con suStatepersistente. Tab navegable (ImGui::BeginTabBar). - 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".
- 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 construirTableInput. Util para auditar regresiones cuando bumpemos versiones. - Side-by-side: renderizar la misma
TableInputcon la version actual a la izquierda y con el modo compatibilidad a la derecha. Diff visual inmediato.
- Mostrar version actual del modulo en header (
- Capture & compare visual: integrar con el sistema
primitives_gallery --captureexistente (golden images) para que cada commit corra screenshots del testbed y compare contra master. Si pixel diff > threshold → CI rojo. - Self-test del modulo: nuevo
tables_playground_module_test.cppque ejerza la API publicadata_table::render()con casos sinteticos. Headless (sin GLFW window) usandoimgui_test_engine(si FN_BUILD_TESTS=ON). Cubre:- Cada
CellRendererenum se renderiza sin crash. events_outrecibe el evento correcto al simular click.- State se mantiene entre frames.
- TQL pipeline produce output esperado.
- Joins respetan strategy.
- Cada
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):
Producir tabla de patrones canonicos en
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.txtdocs/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}aapps/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_testse renombra atables_playground_module_testcon sources nuevos.
- 3.1 Crear
playground.cppcon tab bar + menu acciones. - 3.2 Crear 9 archivos
tables/tbl_*.cppcon cada caso demo. - 3.3 Crear
version_compat.cppcon selector + side-by-side. - 3.4 Anadir API
data_table::module_version()+module_description()amodules/data_table/data_table.h. Implementacion leeversion_generated.h(post 0107c bump a 2.0.0). - 4.1 Crear
test_module.cppheadless conimgui_test_engine(gated porFN_BUILD_TESTS=ON):- 1 test por
CellRendererenum. - 1 test por
TableEventKind. - 1 test de joins.
- 1 test de TQL pipeline.
- Compara cells output via golden TSV.
- 1 test por
- 4.2 Migrar lo aplicable del
self_test.cpplegacy (430 checks) a tests del modulo. Lo que prueba logica pura ya extraida al registry (parse_number, compare, tql parsing, lua) va acpp/functions/core/*_test.cpp. Lo que prueba render() va atest_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 indexpara que el playground actualizado quede registrado (aunque sea playground, suapp.mdpuede declararuses_modules: [data_table_cpp]con min_version 2.0.0).
Decisiones de diseño
- 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.
- Side-by-side downgrade: opcion alternativa era branchear el modulo en
data_table_v1static lib paralelo. Decision: feature flags por version dentro del modulo actual (v_compat_mode). Menos duplicacion, mas pragmatico. module_version()como API publica: alternativa era leerversion_generated.hdesde 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-v2activo. 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:
- Toggles por feature (15+ checkboxes). Cada toggle muta
ColumnSpecoStateen runtime, mismo patron que apps reales hacen en codigo de setup. El usuario QA puede activar/desactivar y ver cambio visual inmediato sin recompilar. - Inyector de eventos: 4 botones — "Simulate ButtonClick", "Simulate RowDoubleClick", "Simulate RowRightClick", "Simulate Drill". Cada uno llama el worker thread pattern de altsnap → verifica que
TableEventapropiado se emite. - 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. - Version selector: dropdown con versiones del modulo (
2.0.0actual,1.4.0compat,1.3.0compat...). Cambio re-renderiza la misma data side-by-side: actual vs version seleccionada. Diff visual inmediato + diff de events emitidos. - Boton "Run --self-test": ejecuta toda la suite headless dentro de la misma ventana. Output en log panel. Util para iteracion rapida sin relanzar.
- Boton "Export golden": corre
--capturesobre cada tab, escribe PNGs agolden/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).