Status sincronizado con master:
- 0001 chat con Claude -> shipped como panel Echo
- 0003 enricher web -> shipped (0028 + 0028b)
- 0026 sistema de jobs -> shipped
- 0027 tipo Webpage -> shipped
- 0028 fetch_webpage -> shipped
- 0028b extract trio -> shipped
- 0031 layout estable -> shipped
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Permite distribuir graph_explorer.exe Windows sin dependencia de WSL
ni del .venv del registry. Tambien funciona en Linux como bundle
autocontenido portable.
Cambios:
1. tools/freeze_python_runtime.sh
- Linux: copia python-build-standalone (uv) ~87 MB,
elimina marker EXTERNALLY-MANAGED, instala wheels.
- Windows: descarga python-3.12.7-embed-amd64.zip oficial
(~12 MB), habilita site-packages, instala wheels via
pip install --target --platform win_amd64.
- Idempotente via runtime/.lock con SHA256 del estado.
- Lee python_runtime_deps del frontmatter de app.md.
2. jobs.cpp::cached_python_runtime() — resolver con cadena:
1. <exe_dir>/runtime/python/{python.exe|bin/python3} (embedded)
2. $FN_PYTHON (env)
3. <registry_root>/python/.venv/bin/python3 (registry_venv)
4. python3 del PATH (system)
Loggea procedencia al iniciar jobs_init.
3. POSIX run_subprocess: usa el runtime resuelto en lugar del
path hardcodeado.
4. Windows run_subprocess: ramifica por needs_wsl. Si embedded
o env, lanza Python Windows nativo via CreateProcessW
directamente (run_path tambien Windows nativo). Solo el
legacy registry_venv sigue por wsl.exe.
5. app.md: nuevos campos python_runtime: true y
python_runtime_deps: [requests, certifi, urllib3].
6. .gitignore extendido con runtime/, projects/, _vendored/,
.vendor.lock, binarios Go de enrichers.
Tests: 26/26 verde — 16 originales + 6 dispatcher fase A + 4
nuevos del resolver fase B (con/sin embed, FN_PYTHON, idempotencia
del freeze script).
Smoke E2E manual: runtime/python/bin/python3 ejecuta web_search
con cwd /tmp y registry_root pasado en ctx, sin tocar el .venv del
registry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Anade siete issues que definen el camino para hacer graph_explorer
distribuible como binario Windows autocontenido (sin WSL):
- 0032 — browser_session enrichers via Playwright (login interactivo,
cookies persistentes, fetch_webpage_browser, web_search_browser).
- 0033 — dispatcher multi-lenguaje (lang: go|python|bash en manifest)
+ runtime Python embebido en <app>/runtime/. 3 fases (A=dispatcher,
B=runtime, C=UI badges).
- 0033b — vendoring de funciones Python por enricher (_vendored/ +
.vendor.lock) para que los enrichers no dependan de registry_root
en runtime.
- 0033c — fn check vendored: drift detection con --fix.
- 0033d — fn index lee python_runtime / python_runtime_deps de app.md.
- 0033e — /compile orquesta freeze + vendor + go builds.
- 0034 — port de los 5 enrichers de sistema a Go. Reusa funciones
Go del registry directamente (no copias). Tests pytest existentes
pasan sin cambios.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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