docs(issues): rewrite 0010 con DuckDB + new 0011 (UI fase 2)
0010 cambia de modelo SQLite CONTAINS_ROW a tier DuckDB: operations.db sigue con grafo + filas promovidas, tablas grandes viven en projects/<proj>/apps/graph_explorer/tables/<slug>.duckdb. 0011 separa la fase 2 (UI expandida + promote/demote + ingesta CSV).
This commit is contained in:
+71
-48
@@ -1,71 +1,94 @@
|
|||||||
---
|
---
|
||||||
id: 0010
|
id: 0010
|
||||||
title: Nodo tabla — contenedor cuadrado con filas que son nodos del grafo
|
title: Nodo tabla — DuckDB foundation + render colapsado
|
||||||
status: pending
|
status: pending
|
||||||
priority: medium
|
priority: high
|
||||||
created: 2026-04-30
|
created: 2026-04-30
|
||||||
|
revised: 2026-05-01
|
||||||
depends_on: [0004, 0005, 0008]
|
depends_on: [0004, 0005, 0008]
|
||||||
---
|
---
|
||||||
|
|
||||||
## Objetivo
|
## Objetivo
|
||||||
|
|
||||||
Un tipo especial de nodo, **Table**, que se renderiza en el viewport como
|
Tier de almacenamiento tabular para nodos `Table` que pueden contener millones
|
||||||
un rectangulo (no circulo) y agrupa visualmente N entidades del grafo. Cada
|
de filas sin saturar `operations.db` ni el grafo. Las "filas" viven en un
|
||||||
fila de la tabla = un nodo real del grafo (con su `type_ref`, sus fields,
|
`.duckdb` por proyecto (o por tabla) y se promueven a entidades reales del
|
||||||
sus tags). Las filas se pueden **extraer** (salen al canvas como nodos
|
grafo solo cuando se necesita interactuar con ellas individualmente
|
||||||
sueltos) y **meter** (un nodo suelto entra como fila).
|
(relaciones, edicion, etc.).
|
||||||
|
|
||||||
Distinto del issue 0004 (vista tabla global por tipo): ese es una **ventana
|
Esta issue cubre **fase 1** — vendoring de DuckDB, funciones `tableview_*`
|
||||||
auxiliar** que tabula entidades existentes; este es un **nodo en el grafo**
|
core, y render colapsado del nodo Table en el viewport. La fase 2 (UI
|
||||||
que existe en `entities` y posee filas via relaciones.
|
expandida, paginacion, promote/demote, ingesta CSV/Parquet) va en issue
|
||||||
|
0011.
|
||||||
|
|
||||||
## Modelo de datos
|
## Modelo de datos
|
||||||
|
|
||||||
- El nodo tabla es una entidad normal con `type_ref = 'Table'` y metadata:
|
**Dos tiers** por proyecto:
|
||||||
```json
|
|
||||||
{
|
|
||||||
"row_type": "Person", // tipo esperado de las filas (puede ser vacio = mixto)
|
|
||||||
"columns": ["name","age","email"], // subset de fields del row_type a mostrar como columnas
|
|
||||||
"expanded": true // estado de UI persistido
|
|
||||||
}
|
|
||||||
```
|
|
||||||
- La pertenencia se modela con relaciones:
|
|
||||||
- `name = "CONTAINS_ROW"`, `from_entity = <table_id>`, `to_entity = <row_entity_id>`, `order = N`.
|
|
||||||
- Una fila puede pertenecer a varias tablas (varias relaciones `CONTAINS_ROW` apuntando al mismo nodo). Confirmado en la conversacion.
|
|
||||||
- Las columnas pueden ser fijas (`row_type` definido → columnas = subset de los `fields` de ese tipo) o libres (definidas por el creador de la tabla en `columns`).
|
|
||||||
|
|
||||||
## Render en viewport
|
```
|
||||||
|
projects/<proj>/apps/graph_explorer/
|
||||||
|
operations.db # SQLite — grafo (entities + relations + filas promovidas)
|
||||||
|
tables/
|
||||||
|
<slug>.duckdb # DuckDB — bulk tabular (millones de filas)
|
||||||
|
```
|
||||||
|
|
||||||
- Forma: `square` o `rounded_square` con tamano dependiente del numero de filas (clamp a min/max).
|
El nodo Table es una entidad normal con `type_ref = 'Table'` y metadata que
|
||||||
- Cuando esta **colapsada**: caja con titulo + contador (`Table · 23 filas`).
|
apunta a su dataset DuckDB. **No contiene filas internamente** — es una vista.
|
||||||
- Cuando esta **expandida**: caja crece y dibuja un grid interno (filas x columnas) con los valores principales. Las filas son arrastrables individualmente.
|
|
||||||
- Las relaciones `CONTAINS_ROW` no se dibujan como aristas normales (serian ruido visual). En su lugar, una fila extraida muestra una arista fina punteada hacia su tabla de origen.
|
|
||||||
- Aristas entrantes/salientes del nodo tabla se dibujan al borde del rectangulo, no al centro.
|
|
||||||
|
|
||||||
## Operaciones
|
```json
|
||||||
|
{
|
||||||
|
"duckdb_path": "tables/sospechosos.duckdb",
|
||||||
|
"table_name": "people",
|
||||||
|
"row_type": "Person",
|
||||||
|
"id_column": "id",
|
||||||
|
"label_column": "name",
|
||||||
|
"columns": ["name","age","email"],
|
||||||
|
"filter_sql": "",
|
||||||
|
"expanded": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
- **Crear tabla**: en context menu del viewport, "New table here". Pide `row_type` opcional. Crea entidad `Table` y la posiciona donde el click.
|
Una fila promovida es una entidad en `operations.db` con metadata de origen:
|
||||||
- **Anadir fila** (tabla expandida o seleccionada): boton "+ row" que crea una entidad nueva con `type_ref = row_type` (si esta definido) y la engancha via `CONTAINS_ROW`.
|
|
||||||
- **Extraer fila**: borra la relacion `CONTAINS_ROW`. La fila queda como nodo libre, posicionada al lado de la tabla.
|
```json
|
||||||
- **Extraer multiples**: shift+click en filas dentro de la tabla expandida, "Extract selected".
|
{
|
||||||
- **Meter fila**: drag de un nodo sobre el rectangulo de una tabla. Confirm dialog si su `type_ref` no coincide con `row_type` de la tabla.
|
"source": {
|
||||||
- **Editar fila**: doble-click en fila → abre Inspector con esa entidad seleccionada.
|
"duckdb": "tables/sospechosos.duckdb",
|
||||||
|
"table": "people",
|
||||||
|
"row_id": "p_42"
|
||||||
|
},
|
||||||
|
"name": "Ana Lopez",
|
||||||
|
"age": 33
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Cambios en codigo
|
## Cambios en codigo
|
||||||
|
|
||||||
- `entity_ops`:
|
- **Vendor DuckDB** en `cpp/vendor/duckdb/` (amalgamation o precompiled). Add
|
||||||
- `bool table_create(db_path, name, row_type, columns_csv, char* out_id)`.
|
library en `cpp/CMakeLists.txt`.
|
||||||
- `bool table_add_row(db_path, table_id, char* out_row_id)` (crea entidad + relacion CONTAINS_ROW).
|
- Nuevo paquete `cpp/functions/duck/`:
|
||||||
- `bool table_extract_row(db_path, table_id, row_id)` (borra solo la relacion).
|
- `duck_open(path) -> duckdb_database` (con `duckdb_open` + `duckdb_connect`).
|
||||||
- `bool table_attach_row(db_path, table_id, row_id, int order)`.
|
- `duck_query(conn, sql, params) -> result` wrapper.
|
||||||
- `bool table_list_rows(db_path, table_id, vector<string>* out_row_ids)`.
|
- `entity_ops` (o `tableview.{h,cpp}` en la app) — funciones a nivel de app:
|
||||||
- Renderer del viewport (`graph_viewport.cpp` y/o `graph_renderer`): branch para `is_table_node` (detectado por `type_ref == "Table"`) que dibuja rectangulo + grid expandido y devuelve hit-testing por filas individuales.
|
- `tableview_create(ops_db, duckdb_path, duck_table, row_type, char* out_id)`
|
||||||
- `graph_load_from_operations`: filtrar las aristas `CONTAINS_ROW` para que no entren en el layout fisico (no aplican fuerzas).
|
crea entidad `Table` con metadata + commit en `operations.db`.
|
||||||
|
- `tableview_count(duckdb_path, sql_filter, int64_t* out)`.
|
||||||
|
- `tableview_page(duckdb_path, sql_filter, offset, limit, vector<TablePageRow>* out)`.
|
||||||
|
- `TablePageRow` lleva los campos del `columns[]` resueltos a string +
|
||||||
|
`promoted` (LEFT JOIN contra `ops.entities`).
|
||||||
|
- `graph_load_from_operations`: filtrar relaciones `CONTAINS_ROW` (heredado
|
||||||
|
del modelo viejo, ya no se emiten pero por si se topa con dbs antiguas).
|
||||||
|
- `views.cpp`:
|
||||||
|
- Detectar nodos `type_ref == "Table"` al renderizar etiquetas/contadores.
|
||||||
|
- Overlay con `ImGui::GetForegroundDrawList()` por cada nodo Table:
|
||||||
|
rectangulo redondeado + label "Table · N filas".
|
||||||
|
|
||||||
## Definicion de hecho
|
## Definicion de hecho
|
||||||
|
|
||||||
- Crear tabla, anadir filas, extraer y meter filas funciona round-trip via SQLite.
|
- DuckDB compila y linka en linux + windows (cmake target).
|
||||||
- Tabla colapsada y expandida se renderizan correctamente en el viewport.
|
- Smoke test: abrir un `.duckdb` vacio, crear tabla con 1M filas (CTAS desde
|
||||||
- Doble-click en fila enfoca el Inspector con esa entidad.
|
`range`), correr `SELECT COUNT(*)` < 100 ms.
|
||||||
- Una fila puede pertenecer a varias tablas sin duplicarse.
|
- `tableview_create` + `tableview_count` + `tableview_page` con tests.
|
||||||
- Borrar la tabla pregunta: "borrar tabla y todas sus filas" o "extraer filas y borrar solo la tabla".
|
- Un nodo `type_ref='Table'` en el grafo se renderiza con un cuadrado overlay
|
||||||
|
encima del circulo GPU, con contador de filas obtenido por `tableview_count`.
|
||||||
|
- El contador refresca al recargar el grafo o tras un INSERT en su DuckDB.
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
---
|
||||||
|
id: 0011
|
||||||
|
title: Nodo tabla — UI expandida, promote/demote, ingesta CSV/Parquet
|
||||||
|
status: pending
|
||||||
|
priority: high
|
||||||
|
created: 2026-05-01
|
||||||
|
depends_on: [0010]
|
||||||
|
---
|
||||||
|
|
||||||
|
## Objetivo
|
||||||
|
|
||||||
|
Fase 2 del nodo tabla. Sobre el cimiento DuckDB de 0010, anade UI completa:
|
||||||
|
expandir nodo Table en el viewport para ver paginas de filas, promover una
|
||||||
|
fila a entidad del grafo (nodo libre + arista punteada hacia su Table) y
|
||||||
|
demover de vuelta. Ingesta de CSV / Parquet como punto de entrada para
|
||||||
|
materializar tablas grandes.
|
||||||
|
|
||||||
|
## UI expandida
|
||||||
|
|
||||||
|
- Click sobre nodo Table -> selecciona; doble click -> toggle `expanded` en
|
||||||
|
metadata.
|
||||||
|
- Cuando expanded:
|
||||||
|
- El cuadrado overlay crece para acomodar grid de cabecera + ~20 filas
|
||||||
|
visibles. Mas filas exigen scroll dentro del overlay.
|
||||||
|
- Cabecera con nombres de `columns[]`. Anchura proporcional al texto, max
|
||||||
|
cap a un % del overlay.
|
||||||
|
- Filas paginadas via `ImGuiListClipper` + `tableview_page(offset,limit)`.
|
||||||
|
- Indicador "promovida" (chip o color) en filas que ya estan en `entities`.
|
||||||
|
- Doble-click en fila -> abre Inspector con esa entidad si esta promovida;
|
||||||
|
si no, la promueve y abre Inspector.
|
||||||
|
- Aristas entrantes/salientes del nodo Table se siguen dibujando al centro
|
||||||
|
(mejora a "al borde" se aplaza).
|
||||||
|
|
||||||
|
## Promote / demote
|
||||||
|
|
||||||
|
- Context menu sobre fila visible (overlay expandido):
|
||||||
|
- "Promote to graph node": crea entidad en `operations.db` con metadata de
|
||||||
|
origen, posiciona el nodo al lado del Table y dibuja arista punteada
|
||||||
|
hacia el Table (overlay).
|
||||||
|
- "Demote": deletea la entidad. La fila sigue viva en DuckDB.
|
||||||
|
- Una fila puede estar promovida una sola vez por (duckdb_path, table,
|
||||||
|
row_id) — el helper de promocion debe checar y hacer no-op idempotente.
|
||||||
|
|
||||||
|
## Ingesta
|
||||||
|
|
||||||
|
- Boton/comando "Import dataset..." en menu o context menu del canvas.
|
||||||
|
- Modal con:
|
||||||
|
- Path al CSV / Parquet / JSON.
|
||||||
|
- Nombre de la tabla DuckDB destino.
|
||||||
|
- row_type (combo con los entity types del proyecto + "(none)").
|
||||||
|
- Boton "Import" -> ejecuta:
|
||||||
|
```sql
|
||||||
|
CREATE TABLE <name> AS SELECT * FROM read_csv_auto('<path>');
|
||||||
|
```
|
||||||
|
sobre `tables/<slug>.duckdb` (crea el .duckdb si no existe).
|
||||||
|
- Tras import, crea automaticamente un nodo Table en el viewport apuntando
|
||||||
|
a la nueva tabla.
|
||||||
|
|
||||||
|
## Cambios en codigo
|
||||||
|
|
||||||
|
- `tableview.{h,cpp}`:
|
||||||
|
- `tableview_promote_row(ops_db, duckdb_path, duck_table, row_id, row_type, out_entity_id)`.
|
||||||
|
- `tableview_demote_row(ops_db, entity_id)`.
|
||||||
|
- `tableview_ingest_file(duckdb_path, file_path, dest_table, *file_kind)`.
|
||||||
|
- `views.cpp`:
|
||||||
|
- Render expandida via overlay. Hit-testing por fila (rect intersection).
|
||||||
|
- Modal "Import dataset...".
|
||||||
|
- `main.cpp`:
|
||||||
|
- Wire context menu items. Recargar grafo tras promote/demote/ingest.
|
||||||
|
|
||||||
|
## Definicion de hecho
|
||||||
|
|
||||||
|
- Toggle expanded persiste en `entities.metadata` (JSON write).
|
||||||
|
- Tabla con 1M filas se navega con scroll fluido (paginacion 200 filas).
|
||||||
|
- Promote: la entidad creada aparece como nodo libre adyacente al Table,
|
||||||
|
unida por arista punteada (visual solamente — no es relacion en BD).
|
||||||
|
- Demote: el nodo desaparece, la fila sigue contandose en `tableview_count`.
|
||||||
|
- Ingesta de CSV de 100k filas tarda < 5 s y deja la tabla lista para mostrar.
|
||||||
|
- Doble-click en fila no promovida la promueve y enfoca Inspector.
|
||||||
Reference in New Issue
Block a user