From 951a77ec7f0d43cf00ab312dd92141ba539ee7ef Mon Sep 17 00:00:00 2001 From: Egutierrez Date: Fri, 15 May 2026 14:49:56 +0200 Subject: [PATCH] close issue 0081: tables promoted to registry + fn doctor cpp-apps BeginTable check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - docs/TQL.md: añadidas secciones joins, views, main_source, 24 viz tokens completos (extraidos de tql_helpers.cpp), color_rules, fn.* builtins completos (20 funciones), funciones bloqueadas del sandbox, tabla de estado de implementacion actualizada. Nota al pie referencia los 129 checks roundtrip (41 emit + 88 apply). - functions/infra/audit_cpp_apps.go: añadida AuditCppTableMigration() que escanea .cpp de cada app imgui buscando ImGui::BeginTable; status CANDIDATE/MIXED/clean segun si usa data_table_cpp_viz en uses_functions. - cmd/fn/doctor.go: fn doctor cpp-apps ahora incluye seccion BeginTable migration con tabwriter CANDIDATE/MIXED; --json produce {conformance, table_migration}. doctorAll incluye cpp_table_migration en el mapa JSON. - .claude/rules/fn_doctor.md: tabla de subcomandos y acciones complementarias actualizadas con el nuevo check. - dev/issues/0081 movido a completed/ con status done y notas de deuda documentadas. Co-Authored-By: Claude Sonnet 4.6 --- .claude/rules/fn_doctor.md | 13 +- cmd/fn/doctor.go | 43 ++- .../0081-tables-promote-registry.md | 14 +- docs/TQL.md | 309 ++++++++++++++++-- functions/infra/audit_cpp_apps.go | 117 +++++++ 5 files changed, 470 insertions(+), 26 deletions(-) rename dev/issues/{ => completed}/0081-tables-promote-registry.md (88%) diff --git a/.claude/rules/fn_doctor.md b/.claude/rules/fn_doctor.md index 38251bd6..a68171f4 100644 --- a/.claude/rules/fn_doctor.md +++ b/.claude/rules/fn_doctor.md @@ -20,10 +20,18 @@ fn doctor sync # Solo drift pc_locations BD vs disco local fn doctor uses-functions # Solo audit imports reales vs uses_functions fn doctor unused # Solo funciones huerfanas del registry fn doctor cpp-apps # Conformidad C++ con cpp/PATTERNS.md (cfg.about/log, no app_menubar manual, no DockSpace duplicado) + # + check BeginTable inline: CANDIDATE (no migrado) / MIXED (parcial) / silencio (limpio) fn doctor --json # Salida JSON (cualquier subcomando) — para agentes/scripts ``` +`fn doctor cpp-apps` produce dos secciones: +1. Conformance (cfg.about/log, fn::run_app, menubar, DockSpace) — una fila por app imgui. +2. BeginTable migration (issue 0081) — solo apps con `ImGui::BeginTable` inline: + - `CANDIDATE`: N tablas inline sin `data_table_cpp_viz` en uses_functions. Considerar migracion. + - `MIXED`: N tablas inline con `data_table_cpp_viz` ya declarado. Migracion parcial OK. + - silencio: 0 BeginTable inline (limpio o completamente migrado). + ### Mapeo subcomando → funcion del registry | Subcomando | Funcion | @@ -33,7 +41,8 @@ fn doctor --json # Salida JSON (cualquier subcomando) — para agentes | `sync` | `pc_locations_drift_go_infra` | | `uses-functions` | `audit_uses_functions_go_infra` | | `unused` | `find_unused_functions_go_infra` | -| `cpp-apps` | `audit_cpp_apps_go_infra` | +| `cpp-apps` (conformance) | `audit_cpp_apps_go_infra` | +| `cpp-apps` (table migration) | `audit_cpp_table_migration_go_infra` (inline en `audit_cpp_apps.go`) | Cada subcomando es un wrapper fino. Toda la logica vive en la funcion. Si quieres usar la salida en otro programa Go, importa la funcion directamente. @@ -64,6 +73,8 @@ Texto humano por defecto (tabwriter). `--json` produce array/objeto serializable | `manual_DockSpaceOverViewport_*` | Borrar la llamada o setear `cfg.auto_dockspace = false` si la app gestiona docking propio | | `missing_cfg_about` / `missing_cfg_log` | Anadir `cfg.about = {...}` / `cfg.log = {".log", 1}` antes de `fn::run_app` | | `app.md_missing_*` | Regenerar via plantilla del scaffolder (`/new-cpp-app`) o anadir campos a mano | +| cpp-apps BeginTable `CANDIDATE` | App tiene N `ImGui::BeginTable` sin migrar. Abrir rama TBD, reemplazar tablas por `data_table::render()` via `fn_table_viz`, añadir `data_table_cpp_viz` a `uses_functions` en `app.md` | +| cpp-apps BeginTable `MIXED` | Migracion parcial en curso. Continuar wave por wave hasta que no queden BeginTable inline | | Backup viejo | `backup_all_bash_pipelines ~/backups/fn_registry` | ### Para agentes diff --git a/cmd/fn/doctor.go b/cmd/fn/doctor.go index f2413ce0..72efc756 100644 --- a/cmd/fn/doctor.go +++ b/cmd/fn/doctor.go @@ -123,6 +123,11 @@ func doctorAll(root string, jsonOut bool) { } else { all["cpp_apps_error"] = err.Error() } + if v, err := infra.AuditCppTableMigration(root); err == nil { + all["cpp_table_migration"] = v + } else { + all["cpp_table_migration_error"] = err.Error() + } if v, err := infra.AuditMlEnv(root); err == nil { all["ml"] = v } else { @@ -168,10 +173,21 @@ func doctorCppApps(root string, jsonOut bool) { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } + tableAudits, err2 := infra.AuditCppTableMigration(root) + if err2 != nil { + fmt.Fprintf(os.Stderr, "warning: table migration audit failed: %v\n", err2) + tableAudits = nil + } + if jsonOut { - emit(audits) + emit(map[string]any{ + "conformance": audits, + "table_migration": tableAudits, + }) return } + + // Conformance section. bad := 0 w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "STATUS\tAPP\tISSUES") @@ -187,6 +203,31 @@ func doctorCppApps(root string, jsonOut bool) { } w.Flush() fmt.Printf("\n%d/%d C++ apps conform.\n", len(audits)-bad, len(audits)) + + // BeginTable migration section. + if len(tableAudits) == 0 { + return + } + hasMigrationNotes := false + for _, t := range tableAudits { + if t.Status != "clean" { + hasMigrationNotes = true + break + } + } + if !hasMigrationNotes { + return + } + fmt.Println("\n--- BeginTable migration (issue 0081) ---") + tw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(tw, "STATUS\tAPP\tTABLES\tMESSAGE") + for _, t := range tableAudits { + if t.Status == "clean" { + continue + } + fmt.Fprintf(tw, "%s\t%s\t%d\t%s\n", strings.ToUpper(t.Status), t.AppID, t.BeginTableCount, t.Message) + } + tw.Flush() } func doctorArtefacts(root string, jsonOut bool) { diff --git a/dev/issues/0081-tables-promote-registry.md b/dev/issues/completed/0081-tables-promote-registry.md similarity index 88% rename from dev/issues/0081-tables-promote-registry.md rename to dev/issues/completed/0081-tables-promote-registry.md index 2dc45568..3d65d9c2 100644 --- a/dev/issues/0081-tables-promote-registry.md +++ b/dev/issues/completed/0081-tables-promote-registry.md @@ -1,16 +1,22 @@ --- id: 0081 title: tables playground — promote a registry + migrar apps C++ (fase 12) -status: partial +status: done priority: high created: 2026-05-12 -updated: 2026-05-13 +updated: 2026-05-15 notes: | 0081-A DONE: 20 types extraidos a cpp/functions/core/data_table_types.h con .md por type (17 core + 3 viz). Playground includes via "core/data_table_types.h", no duplicacion. 603 tests pass, e2e linux+windows OK. - 0081-B..L PENDING: extraer functions (compute_stage, tql_emit/apply, lua_engine, tql_to_sql, - join_tables, viz_render, data_table) + fn_table_viz lib + migrar 5 apps. + 0081-B..L DONE (2026-05-15): 10 funciones registry (8 core + 2 viz), 1 lib fn_table_viz, + 3 apps migradas (chart_demo no aplica, graph_explorer parcial 1/9, registry_dashboard parcial 8/12), + fn doctor cpp-apps check anadido (BeginTable inline detection: CANDIDATE/MIXED), + docs/TQL.md actualizado con joins, views, main_source, 24 viz tokens, color_rules, + derived columns, fn.* sandbox completo (20 builtins), funciones bloqueadas. + Deuda: sqlite_api + deploy_server NO migrados (Go apps, requieren TS table system aparte); + graph_explorer + registry_dashboard + otras apps C++ marcadas CANDIDATE por fn doctor + (migrar en waves futuras con rama TBD dedicada por app). related_components: [cpp/apps/primitives_gallery/playground/tables, cpp/functions, fn_framework] --- diff --git a/docs/TQL.md b/docs/TQL.md index fc56afc4..73875309 100644 --- a/docs/TQL.md +++ b/docs/TQL.md @@ -58,7 +58,7 @@ Independiente de los datos. La misma query puede renderizarse de N formas con un ### Implicaciones para TQL -TQL adopta esa separacion: `stages` (data) + `display` + `columns` (viz). Mismo patron, sintaxis Lua. +TQL adopta esa separacion: `stages` (data) + `display` + `columns` (viz) + `views` (paneles adicionales). Mismo patron, sintaxis Lua. Cuando un boton futuro "Add visualization" se construya, anade un nuevo `display` + viz settings a una query existente sin tocar `stages`. Asi tendremos M visualizaciones (table, bar, line, scatter) sobre los mismos datos transformados. @@ -103,6 +103,10 @@ input_cells (raw dataset) return { version = 1, display = "table", + main_source = "functions", -- opcional: nombre de la fuente principal + + -- JOINS: unir tablas adicionales antes de stage 0 + joins = { ... }, -- opcional -- DATA: pipeline de transformacion stages = { @@ -129,9 +133,14 @@ return { color_rules = { {equals = "0.0", color = "#e08060"} }}, {name = "internal", type = "string", visible = false, order = 3}, }, - visualization_settings = { - -- Futuro: opciones especificas del display (chart axes, paleta, stack, etc.) + + -- VIEWS: paneles de visualizacion (index 1 = principal, resto = extras) + views = { + {display = "table"}, + {display = "bar", x_col = "lang", y_cols = {"count"}}, }, + + visualization_settings = {}, } ``` @@ -139,6 +148,57 @@ return { --- +## `main_source` + +Campo de cadena opcional. Identifica el nombre de la tabla/fuente principal del dataset. Usado por `tql_to_sql` para generar el `FROM "main_source"` correcto en el SQL emitido. Si esta vacio, el motor usa la tabla por defecto del contexto. + +```lua +main_source = "functions" +``` + +En el SQL emitido: `FROM "functions"`. Util cuando la app expone multiples tablas y el agente necesita especificar explicitamente cual es la base del query. + +--- + +## `joins` + +Lista de joins que se aplican antes de stage 0. Los campos de las tablas unidas se añaden como columnas adicionales accesibles en todos los stages. + +```lua +joins = { + { + alias = "t", -- prefijo para sus columnas ("t.field") + source = "types", -- nombre de la tabla a unir + strategy = "left", -- "left" | "inner" | "right" | "full" + on = {{"id", "t.id"}}, -- pares {col_izq, col_der} + fields = {"t.algebraic", "t.description"}, -- cols a incluir (opcional) + }, + { + alias = "u", + source = "unit_tests", + strategy = "inner", + on = {{"id", "u.function_id"}, {"lang", "u.lang"}}, -- multi-key + }, +} +``` + +**Estrategias:** + +| Token | Semantica SQL | +|---|---| +| `"left"` | `LEFT OUTER JOIN` — todas las filas de la izq, nulls donde no hay match | +| `"inner"` | `INNER JOIN` — solo filas con match en ambas tablas | +| `"right"` | `RIGHT OUTER JOIN` — todas las filas de la der | +| `"full"` | `FULL OUTER JOIN` — todas las filas de ambas tablas | + +Default si `strategy` se omite: `"left"`. + +**Campos tras el join:** accesibles como `"alias.field"` (ej. `"t.algebraic"`) en filters, breakouts, aggregations y expressions. Si `fields` se omite, se incluyen todas las columnas de la tabla unida con prefijo alias. + +**Join multi-key:** `on` es lista de pares; se traduce a `ON l.k1 = r.k1 AND l.k2 = r.k2`. + +--- + ## `filter` Lista de predicados. Multiples filters se combinan con AND implicito. @@ -191,6 +251,15 @@ breakout = { "lang", "domain" } Cada combinacion unica de valores `(lang, domain)` produce una fila en el output. Si `breakout` esta vacio pero hay `aggregation`, todo el dataset se reduce a UNA sola fila. +**Breakout con granularidad de fecha** — sufijo `:granularity` en el nombre de la col: + +```lua +breakout = { "created_at:month", "lang" } +-- equivale a GROUP BY date_trunc('month', created_at), lang +``` + +Granularidades disponibles: `year`, `month`, `week`, `day`, `hour`. + **Disponible solo en stages >= 1.** --- @@ -274,13 +343,97 @@ columns = { **Cols que no aparecen en `columns`**: mantienen su estado UI actual (visible, posicion natural). +### `color_rules` + +Reglas de color condicional por valor exacto. Se aplican al renderizar cada celda de la columna: si el valor de la celda es igual a `equals`, la celda se colorea con `color`. + +```lua +color_rules = { + {equals = "go", color = "#86b56b"}, -- verde para Go + {equals = "py", color = "#6b8eb5"}, -- azul para Python + {equals = "bash", color = "#b58f6b"}, -- naranja para Bash +} +``` + +- Solo soporta igualdad exacta (string match). Para rangos numericos, usa una expression que produzca una etiqueta ("high"/"low") y aplica color_rules sobre esa columna derivada. +- Multiples reglas se evaluan en orden; la primera que hace match gana. +- Si ningun match: color por defecto del tema. + +--- + ## `display` -Tipo de visualizacion. v1 solo `"table"`. Futuro: `"bar"`, `"line"`, `"scatter"`, `"pie"`, `"scalar"`, `"area"`, `"pivot"`. Default: `"table"`. +Tipo de visualizacion del panel principal. Default: `"table"`. + +**Tokens validos (extraidos de `tql_helpers.cpp`):** + +| Token | Tipo de chart | +|---|---| +| `"table"` | Tabla de datos (default) | +| `"bar"` | Barras horizontales | +| `"column"` | Barras verticales | +| `"grouped_bar"` | Barras agrupadas por categoria | +| `"stacked_bar"` | Barras apiladas | +| `"line"` | Lineas | +| `"area"` | Area rellena | +| `"stairs"` | Escalera (step function) | +| `"scatter"` | Dispersion XY | +| `"bubble"` | Dispersion XY con tamano variable | +| `"histogram"` | Histograma 1D | +| `"hist2d"` | Histograma 2D | +| `"heatmap"` | Mapa de calor | +| `"boxplot"` | Caja y bigotes | +| `"stem"` | Stem plot | +| `"errorbars"` | Barras de error | +| `"pie"` | Sectores (pie chart) | +| `"donut"` | Donut | +| `"funnel"` | Embudo | +| `"waterfall"` | Cascada | +| `"kpi"` | Metrica KPI (numero grande) | +| `"kpi_grid"` | Grid de KPIs | +| `"candlestick"` | Velas (OHLC) | +| `"radar"` | Radar / spider | + +Token invalido: `tql_apply` genera warning `"unknown display"` y cae a `"table"`. + +--- + +## `views` + +Array de paneles de visualizacion. El indice 1 es el panel principal (equivale al `display` + `viz_config` del State); el resto son paneles extra que se muestran junto a la tabla. + +```lua +views = { + -- Panel 0 (principal) + {display = "bar", x_col = "lang", y_cols = {"count"}, color = "#86b56b"}, + -- Panel 1 (extra) + {display = "pie", cat_col = "lang", y_cols = {"sum_size_kb"}, show_legend = true}, +} +``` + +**Campos por panel:** + +| Campo | Tipo | Para que | +|---|---|---| +| `display` | string | Token de tipo de chart (ver tabla `display`) | +| `x_col` | string | Columna para eje X (bar, column, line, area, scatter, bubble, etc.) | +| `y_cols` | `{string,...}` | Columnas para eje Y. Multiple = multiple series | +| `cat_col` | string | Columna de categorias (pie, donut, funnel, radar) | +| `size_col` | string | Columna para tamano del burbuja (bubble) | +| `color` | string | Color primario `"#rrggbb"`. Sirve para series unicas o acento | +| `hist_bins` | int | Numero de bins para histogram / hist2d | +| `pie_radius` | float | Radio del donut interior (donut, 0.0 = pie solido) | +| `show_legend` | bool | Mostrar leyenda. Default `true` | +| `show_markers` | bool | Puntos en lineas/area. Default `false` | +| `locked` | bool | Panel fijo — el usuario no puede cerrarlo ni cambiar tipo | + +Si `views` se omite, el emit lo serializa con un panel minimo que replica `state.display`. + +--- ## `visualization_settings` -Reservado para configuracion especifica por tipo de display. v1 vacio. Futuro: +Reservado para configuracion especifica por tipo de display. v1 siempre vacio (`{}`). Emitido por `tql_emit` para mantener el round-trip completo. Futuro: ```lua visualization_settings = { @@ -293,6 +446,8 @@ visualization_settings = { Sintaxis Metabase: las keys con `.` van entre brackets `[]`. +--- + ## `sort` Lista de clauses. Multi-sort por orden de aparicion (primera = primaria). @@ -318,6 +473,8 @@ Pregunta: "Para las funciones puras con cobertura >= 80%, agrupa por lenguaje y ```lua return { + version = 1, + display = "table", stages = { -- Stage 0: Raw + filter { @@ -350,6 +507,44 @@ return { --- +## Ejemplo con join + views + +```lua +return { + version = 1, + display = "bar", + main_source = "functions", + joins = { + { + alias = "u", + source = "unit_tests", + strategy = "left", + on = {{"id", "u.function_id"}}, + fields = {"u.name"}, + }, + }, + stages = { + { filter = {{"=", "lang", "go"}} }, + { + breakout = {"domain"}, + aggregation = {{"count"}, {"distinct", "id"}}, + sort = {{"desc", "count"}}, + }, + }, + columns = { + {name = "domain", type = "string", visible = true, order = 1}, + {name = "count", type = "int", visible = true, order = 2}, + }, + views = { + {display = "bar", x_col = "domain", y_cols = {"count"}, show_legend = false}, + {display = "donut", cat_col = "domain", y_cols = {"count"}, show_legend = true}, + }, + visualization_settings = {}, +} +``` + +--- + ## Drill-down (semantica) Si el usuario interactua con una celda agrupada del stage N, hace **drill-down**: @@ -387,8 +582,7 @@ Las strings dentro de `expressions` siguen el mini-DSL Lua de columnas custom. R - Type-aware: cell de col Int/Float llega como number; Bool como boolean; resto como string. Vacia = nil. - UTF-8 ok en nombres `[año]`. - Comentarios `--` y `--[[ ]]` respetados. -- Builtins disponibles via `fn.*`: `upper, lower, length, substring, contains, starts_with, ends_with, replace, trim, concat, to_number, to_string, to_bool, is_null, is_empty, coalesce, parse_date, year, month, day`. -- Sandbox: sin `io`, `require`, `dofile`, `loadfile`, `load`, `package`, `debug`. `os` recortado a `date/time/difftime/clock`. +- Nombres de cols con espacios y puntos soportados en brackets: `[col con espacio]`, `[alias.field]`. Ejemplos: @@ -397,21 +591,73 @@ Ejemplos: fn.concat([lang], ":", [domain]) -- string compose if [coverage_pct] >= 90 then "well" else "low" end fn.year([updated_at]) -- date helper +fn.coalesce([error_type], "none") -- null handling ``` --- +## Funciones Lua disponibles (`fn.*`) + +El sandbox expone estas funciones via la tabla global `fn`. Registradas en `lua_engine.cpp::register_builtins`: + +| Funcion | Firma | Que hace | +|---|---|---| +| `fn.upper(s)` | string -> string | Convierte a mayusculas (ASCII) | +| `fn.lower(s)` | string -> string | Convierte a minusculas (ASCII) | +| `fn.length(s)` | string -> int | Longitud en bytes (`strlen`); nil -> 0 | +| `fn.substring(s, start [, len])` | string, int[, int] -> string | Subcadena 1-based; len omitido = hasta el final | +| `fn.contains(haystack, needle)` | string, string -> bool | True si needle aparece en haystack | +| `fn.starts_with(s, prefix)` | string, string -> bool | True si s empieza por prefix | +| `fn.ends_with(s, suffix)` | string, string -> bool | True si s termina por suffix | +| `fn.replace(s, find, repl)` | string, string, string -> string | Reemplaza todas las ocurrencias de find por repl | +| `fn.trim(s)` | string -> string | Elimina espacios/tabs/newlines del inicio y fin | +| `fn.concat(...)` | vararg -> string | Concatena N argumentos como string | +| `fn.to_number(s)` | string -> number\|nil | Parsea a numero; nil si no parseable | +| `fn.to_string(x)` | any -> string | Convierte a string (usa `luaL_tolstring`) | +| `fn.to_bool(x)` | any -> bool | True si `"true"` o `"1"` | +| `fn.is_null(x)` | any -> bool | True si x es nil | +| `fn.is_empty(x)` | any -> bool | True si x es nil o string vacia | +| `fn.coalesce(...)` | vararg -> any | Devuelve el primer argumento no-nil | +| `fn.parse_date(s)` | string -> table\|nil | Parsea `"YYYY-MM-DD"` -> `{year, month, day}` | +| `fn.year(s)` | string -> int\|nil | Extrae el año de `"YYYY-..."` | +| `fn.month(s)` | string -> int\|nil | Extrae el mes de `"YYYY-MM-..."` | +| `fn.day(s)` | string -> int\|nil | Extrae el dia de `"YYYY-MM-DD"` | + +Ademas, las librerias Lua estandar `string`, `table`, `math`, `os` (recortado) estan disponibles. + +--- + +## Sandbox — funciones bloqueadas + +El engine aplica el sandbox via `lua_engine.cpp::apply_sandbox`. Globals eliminados: + +| Global | Por que bloqueado | +|---|---| +| `io` | I/O de archivos y stdin/stdout | +| `require` | Carga de modulos externos | +| `loadfile` | Ejecucion de archivos Lua arbitrarios | +| `dofile` | Idem | +| `load` | Compilacion y ejecucion de strings arbitrarias | +| `package` | Sistema de paquetes Lua | +| `debug` | Introspection de call stack / upvalues | + +`os` se sustituye por una version recortada que solo expone: `os.date`, `os.time`, `os.difftime`, `os.clock`. El resto de `os` (ejecutar comandos, salir, setenv, etc.) se elimina. + +Las formulas de expresiones se compilan con `luaL_loadbufferx(..., "t")` — el flag `"t"` rechaza bytecode precompilado (solo acepta texto source). + +--- + ## Restricciones v1 | No soportado | Workaround | |---|---| -| Joins entre tablas | Pre-procesar fuera del registry. | -| Subqueries SQL | Usar stages encadenados (modelo equivalente). | | `HAVING` post-aggregation | Stage siguiente con `filter` sobre cols agregadas. | | `LIMIT` | TBD — añadir como `limit = N` en stage v2. | | Window functions | TBD. | | Custom aggregation Lua | TBD — `{"lua", "col", ""}`. | | Alias custom en aggregation v1 | Crear expression post-grupo. | +| color_rules con rangos numericos | Usar expression que emita etiquetas; aplicar color_rules sobre la etiqueta. | +| Multiples fuentes sin join | Declarar cada fuente adicional en `joins`. | --- @@ -423,11 +669,21 @@ Cuando expongas TQL a un LLM, dale este preambulo: You output TQL — a Lua table that describes a table transformation. Format: return { + version = 1, + display = "table", -- table|bar|column|grouped_bar|stacked_bar|line|area|stairs|scatter| + -- bubble|histogram|hist2d|heatmap|boxplot|stem|errorbars| + -- pie|donut|funnel|waterfall|kpi|kpi_grid|candlestick|radar + main_source = "...", -- optional: name of main table/source + joins = { ... }, -- optional: join additional tables stages = { { filter = {...}, expressions = {...}, sort = {...} }, -- Stage 0 (Raw) { filter = {...}, breakout = {...}, aggregation = {...}, sort = {...} }, -- Stage 1+ ... - } + }, + views = { + {display="...", x_col="...", y_cols={...}, cat_col="...", color="...", ...}, -- panel 0 = main + ... -- extra panels + }, } Rules: @@ -437,6 +693,9 @@ Rules: Available fns: count, sum, avg, min, max, distinct, stddev, median, p25, p75, p90, p99, percentile. - Sort: {{"desc", "col"}, ...}. Multi-sort por orden de la lista. - Expressions value es una expresion Lua. Acceso a cols via [col_name]. +- Joins: alias + source + strategy (left/inner/right/full) + on pairs + optional fields list. +- Views: array de paneles, index 1 = principal. display token from the list above. +- color_rules: [{equals="val", color="#rrggbb"}, ...] dentro de cada entry de columns. The available columns of the current input table are: . The available column types: . @@ -483,19 +742,25 @@ StageOutput compute_stage(const char* const* in_cells, int in_rows, int in_cols, | Feature | Status | |---|---| | `Stage` + `Aggregation` types | done | -| `compute_stage` (filter + group + agg + sort) | done (Phase 1) | +| `compute_stage` (filter + group + agg + sort) | done | | Todas las aggregations (count..percentile) | done | | `aggregation_alias` / `aggregation_type` | done | | Multi-sort por stage | done | -| Tests E2E logica | done (37 checks) | -| `tql_emit` / `tql_apply` (Lua round-trip) | Phase 2 (pendiente) | -| State refactor a `vector` | Phase 3 (pendiente) | -| UI breadcrumb stages + chips por stage | Phase 3 (pendiente) | -| Drill-down interactivo | Phase 3 (pendiente) | -| Show TQL / Apply TQL modals | Phase 2 | -| Multi-sort drag-reorder | Phase 4 | - -Ver `cpp/apps/primitives_gallery/playground/tables/` para la implementacion del playground. +| Tests E2E logica | done (129 checks en tql_emit_test + tql_apply_test) | +| `tql_emit` / `tql_apply` (Lua round-trip) | done | +| `views` (paneles de visualizacion) | done | +| `main_source` | done | +| `joins` (left/inner/right/full, multi-key, fields) | done | +| `color_rules` por columna | done | +| `breakout` con granularidad de fecha | done | +| Lua sandbox (`fn.*` builtins, sin io/require/load) | done | +| 24 tipos de viz (table, bar, column, pie, donut...) | done | +| `tql_to_sql` (SQL DuckDB emit) | done (issue 0080) | +| State refactor a `vector` | done | +| UI breadcrumb stages + chips por stage | done | +| Drill-down interactivo | done | +| Show TQL / Apply TQL modals | done | +| Multi-sort drag-reorder | done | --- @@ -580,3 +845,7 @@ SQL transpile error en derived col 'fullname': - **Agente flow:** TQL default. SQL solo si app linko DuckDB. UI Ask AI muestra toggle SQL solo cuando disponible. Ver issue 0080 + `tql_to_sql.{h,cpp}` para implementacion. + +--- + +*Generado a partir de los tests roundtrip en `cpp/functions/core/tql_emit_test.cpp` y `cpp/functions/core/tql_apply_test.cpp` — 129 checks (41 emit + 88 apply) en verde garantizan compatibilidad del round-trip State <-> Lua.* diff --git a/functions/infra/audit_cpp_apps.go b/functions/infra/audit_cpp_apps.go index 2c99263b..59dc1748 100644 --- a/functions/infra/audit_cpp_apps.go +++ b/functions/infra/audit_cpp_apps.go @@ -157,3 +157,120 @@ func cppHasAutoDockspaceFalse(src string) bool { return strings.Contains(src, "auto_dockspace = false") || strings.Contains(src, "auto_dockspace=false") } + +// CppTableMigrationAudit holds the BeginTable migration status for a single C++ app. +type CppTableMigrationAudit struct { + AppID string `json:"app_id"` + DirPath string `json:"dir_path"` + BeginTableCount int `json:"begin_table_count"` + UsesDataTableViz bool `json:"uses_data_table_viz"` + Status string `json:"status"` // "clean", "candidate", "mixed" + Message string `json:"message"` +} + +// AuditCppTableMigration scans C++ apps (imgui framework) for inline +// ImGui::BeginTable calls and checks whether the app already declares +// data_table_cpp_viz in its uses_functions. +// +// Status values: +// - "clean" — no ImGui::BeginTable found (fully migrated or never had tables) +// - "mixed" — has ImGui::BeginTable AND declares data_table_cpp_viz +// (partial migration OK, wave N in progress) +// - "candidate" — has ImGui::BeginTable but does NOT declare data_table_cpp_viz +// (migration not started; consider data_table_cpp_viz, issue 0081) +func AuditCppTableMigration(registryRoot string) ([]CppTableMigrationAudit, error) { + dbPath := filepath.Join(registryRoot, "registry.db") + dsn := fmt.Sprintf("file:%s?mode=ro&_foreign_keys=on", dbPath) + db, err := sql.Open("sqlite3", dsn) + if err != nil { + return nil, fmt.Errorf("audit_cpp_table_migration: open db: %w", err) + } + defer db.Close() + if err := db.Ping(); err != nil { + return nil, fmt.Errorf("audit_cpp_table_migration: ping db: %w", err) + } + + rows, err := db.Query(`SELECT id, dir_path, COALESCE(framework,''), COALESCE(uses_functions,'') FROM apps WHERE lang = 'cpp' ORDER BY id`) + if err != nil { + return nil, fmt.Errorf("audit_cpp_table_migration: query apps: %w", err) + } + defer rows.Close() + + var results []CppTableMigrationAudit + for rows.Next() { + var id, dir, framework, usesFunctions string + if err := rows.Scan(&id, &dir, &framework, &usesFunctions); err != nil { + continue + } + if framework != "imgui" { + continue + } + + absDir := dir + if !filepath.IsAbs(absDir) { + absDir = filepath.Join(registryRoot, dir) + } + if _, err := os.Stat(absDir); os.IsNotExist(err) { + continue // directory_missing already reported by AuditCppApps + } + + // Count ImGui::BeginTable occurrences across all .cpp files (excluding tests/). + count := 0 + err := filepath.WalkDir(absDir, func(path string, d os.DirEntry, walkErr error) error { + if walkErr != nil { + return nil + } + // Skip test directories. + if d.IsDir() && (d.Name() == "tests" || d.Name() == "test") { + return filepath.SkipDir + } + if d.IsDir() { + return nil + } + if !strings.HasSuffix(d.Name(), ".cpp") { + return nil + } + data, err := os.ReadFile(path) + if err != nil { + return nil + } + src := string(data) + for start := 0; ; { + idx := strings.Index(src[start:], "ImGui::BeginTable(") + if idx < 0 { + break + } + count++ + start += idx + len("ImGui::BeginTable(") + } + return nil + }) + if err != nil { + continue + } + + usesViz := strings.Contains(usesFunctions, "data_table_cpp_viz") + + audit := CppTableMigrationAudit{ + AppID: id, + DirPath: dir, + BeginTableCount: count, + UsesDataTableViz: usesViz, + } + + switch { + case count == 0: + audit.Status = "clean" + audit.Message = "" + case usesViz: + audit.Status = "mixed" + audit.Message = fmt.Sprintf("%d ImGui::BeginTable inline (partial migration OK — data_table_cpp_viz already declared)", count) + default: + audit.Status = "candidate" + audit.Message = fmt.Sprintf("candidate to migrate: %d ImGui::BeginTable inline detected, consider data_table_cpp_viz (issue 0081)", count) + } + + results = append(results, audit) + } + return results, nil +}