Antes: cada reload disparado por enrichers (dirty_counter) ejecutaba
graph_viewport_fit (recentraba camara), recargaba desde SQL con todos
los nodos en (0,0), aplicaba layout_circular si todo estaba en cero, y
los huerfanos quedaban apilados sobre el origen. Si physics estaba ON,
las fuerzas dispersaban todo el grafo violentamente.
Ahora:
- Auto-save de posiciones antes de cada reload — preserva lo que el
usuario ve en pantalla sin pulsar "Save layout".
- No graph_viewport_fit en reloads (solo en primera carga via
load_input(first_load=true)). La camara permanece donde estaba.
- No layout_circular en reloads (mismo guard via first_load).
- Halo placement: nodos huerfanos (en (0,0) tras layout_store_load)
se colocan junto a su primer vecino con coordenadas conocidas,
buscando slot angular libre en radios crecientes (80,140,200,280,400)
con jitter deterministico por user_data. Si no hay vecinos
colocados, se aparcan en columna lateral fuera del bbox.
- Anti-overlap garantizado a min_dist=60 px entre centros.
- Physics siempre OFF tras reload — el usuario las activa
explicitamente.
- Auto-save tambien al inicio de reload_after_mutation (mutaciones
manuales add/delete/duplicate/change_type) por consistencia.
- Refresca entity_index tras reload (los nuevos nodos creados por
enrichers tienen user_data nuevos que el indice anterior no conoce).
Tests visuales: compila limpio, jobs_init continua detectando
enrichers, smoke test del binario OK.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- TableMetadata struct + tableview_get_metadata: lee la metadata de un
nodo Table (path, table, row_type, columns, label_column, expanded...).
- tableview_set_expanded: persiste el flag expanded usando json_set.
- tableview_set_columns: sobrescribe metadata.columns.
- tableview_promote_row: idempotente — si ya existe entidad con
metadata.source.row_id == row_id la devuelve; si no, lee fila completa
desde DuckDB e inserta entity con id 'prom_<type>_<row_id>' y metadata
incluyendo source + columnas.
- tableview_demote_row: DELETE FROM entities (la fila DuckDB no se toca).
- tableview_ingest_file: CREATE TABLE AS SELECT * FROM read_csv_auto/
read_parquet/read_json_auto segun extension del input.
- tableview_list_columns: SELECT * FROM tabla LIMIT 0 -> nombres.
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).
- entity_ops: entity_list_rows (bulk pull id/name/type_ref/status/updated_at).
- AppState::TableRow + cache + filtros (search substring + show_all toggle).
- views_table: tabs por type_ref (alfabetico) o tabla unica con todos los
tipos. ImGui::BeginTable con sort + clipper para >10k filas. Click en
Selectable selecciona el nodo en el viewport (clear + add via
graph_viewport_*).
- views_table_refresh_indices: degree + node_idx por user_data hash.
- main.cpp: panel "Table" en g_panels; cache build tras load_input y
reload_after_mutation.
- views_type_editor: panel "Types" con tabs Entities/Relations.
Entities: name, color picker, shape combo, icon (ti-* + cp preview),
principal_field combo, tabla de Fields (string/int/float/bool/date/url/enum)
con required y enum values CSV; up/down/X por fila.
Relations: name, color, style.
Footer Save / Reload from disk + indicador dirty + error inline.
- views_type_editor_delete_modal: confirm con conteo de entidades en uso.
- types_registry: shape_name() + shape: emit en types_save_yaml para
round-trip estable de la cosmetica editada en UI.
- main.cpp: panel "Types" en g_panels; init types_draft tras load_input;
want_types_save -> save + apply_types_yaml + rebuild atlas + bind +
refresh inspector caches; want_types_reload simetrico; conteo de
uso desde operations.db cuando se abre el modal de delete.
Modulo nuevo que gestiona el sistema de proyectos del issue 0006.
Cada proyecto vive como subcarpeta junto al exe con su operations.db,
types.yaml y graph_explorer.db propios. Helpers:
- project_validate_slug / project_paths / project_list / project_exists
- project_create — bootstrap operations.db con DDL completo (entities,
relations, fts5, triggers, assertions, executions, logs) + types.yaml
semilla (copia de examples/types.yaml o embed si no existe).
- projects_migrate_legacy_layout — mueve operations.db / graph_explorer.db
del cwd a projects/default/ si el directorio projects/ no existe.
- project_settings_load/save/touch — graph_explorer.ini con last_active
y recent (max 5).
- project_reveal_in_explorer — Windows ShellExecute / Linux xdg-open.
CMakeLists registra project_manager.cpp en add_imgui_app.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Dockspace host (PassthruCentralNode) bajo la toolbar para que las
ventanas Viewport/Legend/Inspector/Stats puedan dockearse dentro de la
app principal.
- Toolbar: input "Add node" con auto-deteccion de tipo (text/email/
ip_address/url/domain/phone). Insert en operations.db + reload.
- Context menu (right-click sobre nodo): Change type, Duplicate, Delete,
submenu "Run enricher" (placeholder hasta issues 0001-0003).
- Inspector: vecinos ahora muestran etiqueta de relacion ("-> employs",
"<- owns") usando rel_types[].name como label de arista.
- Default relation label k_default_relation_name="RELATED_TO" para
relaciones creadas sin nombre semantico explicito.
- Indice EntityIndex (FNV1a hash -> sql id) reconstruido tras cada load
para resolver mutaciones desde el grafo en memoria.
Issues planteadas para iteraciones siguientes:
- 0001: chat con Claude sobre el grafo (HTTP + tool-use)
- 0002: enricher GLiNER+GLiREL desde nodo texto
- 0003: enricher web (fetch URL/dominio + extract text)
- 0004: vista tabla por tipo de entidad