Commit Graph

34 Commits

Author SHA1 Message Date
egutierrez fdd169bc35 feat(0037): placement direccional 45 grados de orphans (away from centroide)
Antes los hijos del mismo anchor se distribuian en un anillo de 360
grados alrededor del padre. Cuando un enricher producia 10+ hijos,
se llenaban todas las direcciones y se pisaban nodos preexistentes.

Ahora los hijos se reparten en un abanico de 45 grados (pi/4) saliendo
del anchor en la direccion outward (vector anchor - centroide del resto
del grafo). Si solo hay 1 nodo placed o coincide con el anchor, default
a la derecha (0 rad). Capacidad por anillo restringida al arco
(arc_span * r / min_dist), con fallback de subida de radio en mismo
angulo si el slot ideal colisiona con un nodo no-orphan.

Solo afecta la pasada 2 (orphans con anchor). Pasadas 1 y 3 intactas.
build limpio, 102 pytest passed (WSL) + 91 passed/11 skipped (Windows).

Refs: issues/0037-directional-orphan-placement.md
2026-05-04 01:27:11 +02:00
egutierrez f6f53b60c3 feat(0036f): view menu accion 'Open NodeGroups for selected'
Anyade un item al menu View del framework via el nuevo callback
AppConfig.view_extras. El item:
- Esta enabled solo si la seleccion del viewport (o, en su defecto,
  el inspector) apunta a un nodo con type_ref Table o Group.
- Click → resuelve sql_id via entity_index_lookup, deriva
  NodeGroupsKind del type_ref y llama
  views_node_groups_open(g_app, sql_id, kind, ops_db). La API
  marca focus_request=true (cubierto por 0036c), de modo que la
  window emerge al frente si ya existia.
- Disabled → tooltip 'Select a Table or Group node first' (mostrado
  con AllowWhenDisabled).

Sin atajo de teclado (descartado por el usuario).
Sin submenu de windows abiertas (fase 2).

Refs: issues/0036f-view-menu-open-nodegroups.md
2026-05-04 01:12:58 +02:00
egutierrez 3e71fcc4ca docs(0036e): mark issue as done 2026-05-04 01:06:41 +02:00
egutierrez f4e4dd5a0b feat(0036e): row click en NodeGroups enfoca la entidad (kind-aware)
- Reusa la infra de focus existente (AppState::want_focus_entity /
  focus_entity_id) ya cableada en main.cpp desde 0011.
- kind=Group: single click sobre la fila pone want_focus_entity con
  row.id; tooltip "Click to focus entity in viewport" en hover.
  El doble click sigue funcionando (mismo efecto). El menu contextual
  y el boton Promote-out-of-group quedan intactos.
- kind=Table promovida (row.promoted_entity_id no vacio): single click
  pone want_focus_entity con promoted_entity_id; tooltip de focus.
- kind=Table no promovida: single click es no-op visual; tooltip
  "promote first to focus\n(double click or right click to promote)"
  como hint sutil. El doble click sigue lanzando el flujo de promote
  (legado de 0036c) y el menu contextual ofrece Promote.
- Sin cambios en el handler de main.cpp — la logica de pan/zoom + select
  + load inspector ya existe y se reutiliza tal cual.
- Sin tests Python nuevos: el comportamiento es UI ImGui (no testeable
  desde pytest). 102 passed WSL / 91+11 skipped Windows sin regresion.

Refs: issues/0036e-row-click-focus-viewport.md
2026-05-04 01:06:30 +02:00
egutierrez f0d8a5ad04 feat(0036d): promote kind-aware (Group → clear group_id)
NodeGroups window kind=Group ahora expone un boton SmallButton(TI_ARROW_UP)
por fila que saca la entidad del grupo (group_id = NULL) y dispara
reload del grafo. kind=Table mantiene el comportamiento de issue 0011.

- entity_ops: nueva op `entity_clear_group_id(db, id)` idempotente. Si
  la columna group_id no existe (BD pre-0035a) retorna true como no-op.
  Falla solo si la entidad no existe o SQLite revienta.
- views.cpp: extra columna "promote" en kind=Group, tooltip header
  diferenciado por kind, boton conectado a app.want_clear_group_id_entity.
- main.cpp: handler que ejecuta entity_clear_group_id, marca windows
  como dirty, llama reload_after_mutation y loguea
  `[node_groups] promoted X out of group`.
- gx-cli: flag `node update --clear-group-id` (booleano) y exposicion
  MCP en inputSchema + MCP_DISPATCH defaults para que el agente Echo
  pueda promover via tool calls.
- tests: 3 nuevos CLI (clear, idempotente, combinable con --name) y
  4 MCP (defaults, schema, dispatch end-to-end, idempotente).

WSL: 102 passed (95 base + 7).
Windows: 91 passed, 11 skipped (84 base + 7).

Refs: issues/0036d-promote-kind-aware.md
2026-05-04 01:03:11 +02:00
egutierrez 98e744ea4e docs(0036c): mark issue as done 2026-05-04 00:56:50 +02:00
egutierrez 7277f63985 docs(0036b): mark issue as done 2026-05-04 00:52:36 +02:00
egutierrez d6e13fddc3 feat(0036b): NodeGroups admite kind=Group + loader entities
NodeGroupsWindowState gana un discriminador `kind` (Table | Group) y
un flag `focus_request` (lo consumira 0036c). Por defecto Table, asi
que el flujo historico (DuckDB rows tras expand de un nodo Table) no
cambia.

kind=Group lee directamente operations.db consultando
`entities WHERE group_id = container_id` con columnas fijas
(id, name, type_ref, status, updated_at) ordenadas por updated_at DESC.
Los nuevos loaders viven en node_groups.cpp:

  - node_groups_count_for_group  -> SELECT count(*) ...
  - node_groups_page_for_group   -> SELECT id,name,type_ref,status,
                                     updated_at ... LIMIT ? OFFSET ?

Para columnas, opcion (A) del issue: pre-popular meta.columns con la
lista fija al abrir kind=Group, asi el render se mantiene generico.
NodeGroupsRow.values guarda los 5 campos en ese orden y row.id es la
key natural (= entity_id de la fila — al ser ya entidad, no hace falta
promocionarla).

Render en views.cpp ramifica por kind:

  - Table: layout original [id_col + columns + promoted] con doble
    click -> promote/focus.
  - Group: layout [columns fijas] sin promoted. Doble click sobre la
    fila ya pone want_focus_entity = id (los flujos posteriores 0036c-e
    afinan UX). Right click ofrece "Focus in Inspector".

main.cpp dispatcha por kind al refrescar paginas y, al cerrar via X,
solo llama a node_groups_set_expanded para kind=Table (Group no usa
ese flag).

views_node_groups_windows_sync se hace kind-aware: solo reconcilia
entries kind=Table contra el set de Tables expandidas; no toca las
entries kind=Group (las gestiona views_node_groups_open).

Nueva API publica:

  views_node_groups_open(app, container_id, kind, ops_db)

Crea o reusa la entry, setea focus_request=true y para kind=Group
pre-popula meta.columns + intenta leer `name` del Group para el
titulo. Sin caller todavia — la consume 0036c.

Tests:
  - tests/test_node_groups_loader.py (6 tests) verifica el contrato
    SQL via gx-cli. Nuevo subcomando `gx-cli group page <id>` espejea
    el loader C++ exactamente (mismo SQL); tambien expuesto como tool
    MCP `group_page` para que Echo pueda inspeccionar Groups.

Resultado:
  - WSL: 89 -> 95 passed
  - Windows: 78+11 -> 84+11 passed
  - Build C++ Windows limpio, sin warnings nuevos.
  - Regresion kind=Table: comportamiento identico (mismo render,
    mismo loader DuckDB).

Refs: issues/0036b-kind-discriminator-and-group-loader.md
2026-05-04 00:52:25 +02:00
egutierrez f8b16b2a5a docs(0036a): mark issue as done 2026-05-04 00:43:22 +02:00
egutierrez 092ad2801e docs(0035d): mark issue as done 2026-05-03 14:57:28 +02:00
egutierrez eff273a2d4 docs(0035c): mark issue as done 2026-05-03 14:52:35 +02:00
egutierrez d3da1416f3 feat(0035b): renderer oculta hijos de grupos colapsados + dedup aristas
- AppState anade `group_expanded` (unordered_map<string,bool>) en RAM,
  default vacio = todos los grupos colapsados al arranque. Sin
  persistencia entre sesiones (fase 1).
- `apply_group_filter(GraphData*, db_path, expanded)` consulta
  entities (id, group_id, type_ref) de operations.db, marca como
  ocultos los nodos cuyo group_id apunta a un grupo no expandido,
  compacta `g->nodes` y re-mapea indices de aristas.
- Aristas:
  * Cross-edge (un extremo oculto, otro fuera): se redirige el
    extremo oculto al nodo del grupo. Sin dedup (issue 0035 dec. 5).
  * Internas (ambos extremos en el mismo grupo colapsado): se ocultan.
  * Inter-grupo (ambos en grupos colapsados distintos): dedup por
    par no ordenado (group_a, group_b) + rel_type, una linea por par.
  * Orfanas (group_id apunta a un grupo no presente en grafo): el
    nodo se oculta y sus aristas se descartan.
- Centralizado: el filtro corre en `reload_graph()` cuando se le
  pasa `group_expanded`, y en `load_input()` tras el load inicial.
  Cubre las 4 rutas de carga del app (toolbar reload, mutaciones,
  inspector save, primera carga / switch project).
- Idempotente sobre un grafo ya filtrado y robusto frente a BDs sin
  columna `group_id` (schema antiguo) — no toca el grafo.

Smoke test manual con 3 BDs sintéticas:
- Grupo + 2 children + edges cruzadas/internas: nodes 5→3, edges
  4→3 (internal hidden, cross redirected).
- 2 grupos con 4 cross-edges entre ellos: edges 4→1 (dedup).
- group_id huerfano: nodo oculto + arista descartada.

Build clean en Windows. Tests verdes:
- WSL pytest: 32 passed.
- Windows pytest: 21 passed + 11 skipped.

Refs: issues/0035b-renderer-hides-grouped-children.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 14:48:17 +02:00
egutierrez 4be5734ce5 docs(0035a): mark issue as done 2026-05-03 14:23:33 +02:00
egutierrez fc4f0824da feat(0035a): tipo Group + columna group_id en entities
Plumbing para issue 0035 — agrupacion de resultados de enrichers
cuando exceden umbral. Sin cambios visibles para el usuario todavia.

- Migracion idempotente: ALTER TABLE entities ADD COLUMN group_id si
  no existe (detectado via PRAGMA table_info). Se ejecuta al abrir
  el proyecto en switch_to_project y en el bootstrap inicial.
- Tipo Group en examples/types.yaml (template) y en el types.yaml
  del proyecto default activo en Windows.
- shape=square (regla en types_registry.cpp extendida a Group),
  color=#94A3B8, icon=ti-stack-2.
- Fields: name (req), count (int), enricher (string), batch_id (string).

Refs: issues/0035a-group-type-and-schema.md
2026-05-03 14:23:23 +02:00
egutierrez ee0d26ce2d feat(enrichers): vendoring de funciones Python por enricher (issue 0033b)
Cada enricher con `lang: python` y `uses_functions` no vacio ahora
puede empaquetar las funciones del registry que necesita en
`<enricher>/_vendored/`. El run.py importa de ahi en lugar de
`<registry_root>/python/functions/`, lo que hace al binario
distribuible sin dependencia de un fn_registry montado.

Cambios:

1. tools/vendor_enricher_python.sh
   - Lee `uses_functions` del manifest (filtrando IDs `*_py_*`).
   - Resuelve `file_path` desde registry.db.
   - Copia recursivamente con expansion transitiva: si un fichero
     vendorizado importa siblings del mismo dominio, los siblings
     tambien se copian (resuelve el caso `extract_iocs.py` que
     importa 7 modulos hermanos).
   - Genera `.vendor.lock` con `<id>  <sha256>  <src_path>` por
     funcion declarada para auditoria.
   - Idempotente — si todos los hashes coinciden, no rehace nada.

2. Manifests actualizados con `uses_functions`:
   - fetch_webpage:        normalize_url + html_to_markdown
   - extract_links:        extract_urls
   - extract_text_entities: extract_iocs

3. run.py de los 3 enrichers afectados: importan de `_vendored/`
   si existe, fallback a `<registry_root>/python/functions/` en
   modo dev (mantiene los tests pytest funcionando).

4. app.md: anade `cryptography` a python_runtime_deps porque el
   blob `cybersecurity.cybersecurity` lo importa al top.

5. Tests:
   - test_vendor_script.py — 6 tests del script: layout correcto,
     transitive siblings, lock con SHA256, idempotencia, modulos
     importables en aislamiento.
   - 16 tests de enrichers existentes pasan via vendoring (no usan
     registry_root porque _vendored/ tiene prioridad).

6. Issue 0033b movido a issues/completed/.

Tests: 32/32 verde (16 enrichers + 6 dispatcher + 4 runtime + 6
vendor).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 00:20:41 +02:00
egutierrez 4ef6a5f7db chore(issues): mover 7 issues completadas a issues/completed/
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>
2026-05-03 00:14:58 +02:00
egutierrez 30f6f3758f feat(jobs): runtime Python embebido + cadena de fallback (issue 0033 fase B)
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>
2026-05-02 16:51:02 +02:00
egutierrez 7e0b55d282 docs(issues): marcar 0033 fase A como completada
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-02 16:15:28 +02:00
egutierrez 35ace544d9 docs(issues): roadmap fase 2 navegador + ports Go + runtime embebido
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>
2026-05-02 16:10:35 +02:00
egutierrez 012e2e97a6 fix(layout): layout estable al recargar (issue 0031)
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>
2026-05-01 18:39:59 +02:00
egutierrez 9042110ea2 docs(issues): plan enrichers asincronos + recoleccion web (0026-0030)
Cinco issues que componen el plan:
- 0026: sistema de jobs (infra, contrato wire)
- 0027: tipo Webpage + cache de documentos
- 0028: enricher fetch_webpage (MVP end-to-end)
- 0028b: enrichers extract_domain / extract_links / extract_text_entities
- 0029: variantes CDP (Chrome headless, screenshot)
- 0030: macro "Deep enrich" + expand_domain

Tambien anade los issues previos 0012-0025 que estaban untracked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 18:24:13 +02:00
egutierrez cf279672fe docs(issues): close 0011 — frontmatter status=completed 2026-05-01 01:53:31 +02:00
egutierrez 1065e184cf feat(tableview): helpers fase 2 (issue 0011)
- 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.
2026-05-01 01:52:49 +02:00
egutierrez 082008bc00 feat(table-node): DuckDB foundation + render colapsado (issue 0010)
- tableview.{h,cpp}: capa C sobre DuckDB v1.1.3.
  * tableview_smoke_test (SELECT 42).
  * tableview_count (con sql_filter opcional).
  * tableview_page (LEFT JOIN sobre ops.entities via ATTACH para flag promoted).
  * tableview_create (inserta entidad type_ref='Table' con metadata pointer).
  * tableview_refresh_counts (lee Table entities, count cada DuckDB y cachea
    por user_data hash).
  * tableview_resolve_path (rel a dirname(ops_db) o absoluto).
- AppState::table_node_counts cache, refrescado tras load_input y mutaciones.
- views_table_overlay: rectangulo redondeado overlay ("Table  N") encima
  de cada nodo type_ref='Table'. Sigue camara via cam_x/cam_y/zoom.
- main.cpp:
  * --test-duckdb <path> smoke (SELECT 42).
  * --test-tableview <path> bulk test (1M rows count + page offset).
  * Refresh de counts tras load + reload_after_mutation.
  * Llamada a views_table_overlay despues de graph_labels_draw.
- CMakeLists.txt: link DuckDB::DuckDB + duckdb_copy_runtime.

Smoke tests:
- 1M rows count + page(offset=500k, limit=10) en 0.65 s end-to-end.
- Operations.db con un nodo Table apuntando a duckdb 1M filas: refresh
  reporta correctamente "1 tables, 1000000 total rows".
2026-05-01 01:24:43 +02:00
egutierrez 20d8bbf360 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).
2026-05-01 01:14:52 +02:00
egutierrez 84afa4ce70 feat(table): vista tabla por tipo de entidad (issue 0004)
- 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.
2026-05-01 01:05:03 +02:00
egutierrez f80348d604 feat(types): Type Editor panel — CRUD de tipos en vivo (issue 0007)
- 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.
2026-05-01 00:42:30 +02:00
egutierrez 54aba71bf5 feat(filter): tag chips + FTS5 search en toolbar (issue 0009)
- entity_ops: entity_search_fts (bm25, prefix tokens) + entity_list_by_tags (AND).
- AppState: filter_query_buf, filter_tags, filter_mode (highlight/hide), hits cache.
- views_filter_apply: combina tipos visibles + match-set (FTS query AND tags),
  highlight = color_override con alpha=0x40, hide = clear NF_VISIBLE.
- toolbar: input search + dropdown (max 20, click centra y selecciona),
  chips de tags con boton X, input para anadir tag, combo Highlight/Hide,
  Clear filter.
- Inspector: right-click sobre tag chip lo anade al filtro.
- main.cpp: reapply en cada frame si filter_dirty; cam_x/y al focus_target.
2026-05-01 00:23:26 +02:00
egutierrez 1fbd756583 docs(issues): close 0008 — inspector editable
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:13:19 +02:00
egutierrez d26449af64 docs(issues): close 0005 — type schema fields
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 00:01:02 +02:00
egutierrez a3b5a2cd80 docs(issues): close 0006 — projects subfolders
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:47:08 +02:00
egutierrez c365b1bc43 feat(projects): project_manager module — DDL bootstrap, slug/paths, settings, reveal
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>
2026-04-30 23:46:35 +02:00
egutierrez 491161204e docs(issues): planning issues 0005-0010 (types schema, projects, type editor, inspector, fts, table node)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:40:14 +02:00
egutierrez a36530bb6f feat: docking host + add-node toolbar + node context menu
- 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
2026-04-30 22:55:30 +02:00