--- id: 0010 title: Nodo tabla โ€” contenedor cuadrado con filas que son nodos del grafo status: pending priority: medium created: 2026-04-30 depends_on: [0004, 0005, 0008] --- ## Objetivo Un tipo especial de nodo, **Table**, que se renderiza en el viewport como un rectangulo (no circulo) y agrupa visualmente N entidades del grafo. Cada fila de la tabla = un nodo real del grafo (con su `type_ref`, sus fields, sus tags). Las filas se pueden **extraer** (salen al canvas como nodos sueltos) y **meter** (un nodo suelto entra como fila). Distinto del issue 0004 (vista tabla global por tipo): ese es una **ventana auxiliar** que tabula entidades existentes; este es un **nodo en el grafo** que existe en `entities` y posee filas via relaciones. ## Modelo de datos - El nodo tabla es una entidad normal con `type_ref = 'Table'` y metadata: ```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 = `, `to_entity = `, `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 - Forma: `square` o `rounded_square` con tamano dependiente del numero de filas (clamp a min/max). - Cuando esta **colapsada**: caja con titulo + contador (`Table ยท 23 filas`). - 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 - **Crear tabla**: en context menu del viewport, "New table here". Pide `row_type` opcional. Crea entidad `Table` y la posiciona donde el click. - **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. - **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. - **Editar fila**: doble-click en fila โ†’ abre Inspector con esa entidad seleccionada. ## Cambios en codigo - `entity_ops`: - `bool table_create(db_path, name, row_type, columns_csv, char* out_id)`. - `bool table_add_row(db_path, table_id, char* out_row_id)` (crea entidad + relacion CONTAINS_ROW). - `bool table_extract_row(db_path, table_id, row_id)` (borra solo la relacion). - `bool table_attach_row(db_path, table_id, row_id, int order)`. - `bool table_list_rows(db_path, table_id, vector* out_row_ids)`. - 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. - `graph_load_from_operations`: filtrar las aristas `CONTAINS_ROW` para que no entren en el layout fisico (no aplican fuerzas). ## Definicion de hecho - Crear tabla, anadir filas, extraer y meter filas funciona round-trip via SQLite. - Tabla colapsada y expandida se renderizan correctamente en el viewport. - Doble-click en fila enfoca el Inspector con esa entidad. - Una fila puede pertenecer a varias tablas sin duplicarse. - Borrar la tabla pregunta: "borrar tabla y todas sus filas" o "extraer filas y borrar solo la tabla".