Files
fn_registry/dev/issues/completed/0049k-graph-explorer-app.md
T
egutierrez 336051fef5 feat(0049k): graph_explorer wiring + close issue 0049
- cpp/CMakeLists.txt: register projects/osint_graph/apps/graph_explorer/
  via add_subdirectory pattern (igual que registry_dashboard).
- dev/feature_flags.json: osint_graph_v1 = true (enabled_at 2026-04-30).
- dev/issues/{0049,0049k} → dev/issues/completed/. README index actualizado.

La app vive en su sub-repo dataforge/graph_explorer (push hecho al cerrar).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 00:14:31 +02:00

232 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 0049k — App `graph_explorer` (proyecto `osint_graph`)
## Metadata
| Campo | Valor |
|-------|-------|
| **ID** | 0049k |
| **Estado** | pendiente |
| **Prioridad** | alta |
| **Tipo** | feature — parte de [#0049](0049-osint-graph-viewer.md) — **integracion final + activacion del feature flag** |
## Dependencias
**Bloqueada por:**
- [0049a](0049a-osint-graph-setup.md) — proyecto + sub-repo
- [0049f](0049f-graph-renderer-symbols.md) — renderer completo
- [0049g](0049g-graph-source-operations.md) — source operations.db
- [0049h](0049h-graph-force-layout-gpu.md) — layout GPU
- [0049i](0049i-graph-layouts-static.md) — layouts estaticos + viewport
- [0049j](0049j-graph-labels.md) — labels
**Desbloquea:** activacion del feature flag `osint_graph_v1`.
---
## Objetivo
Construir la app C++ `graph_explorer` en `projects/osint_graph/apps/graph_explorer/`, agnostica del backend, capaz de abrir cualquier `operations.db` del registry y visualizarlo con shapes/iconos/layouts/filtros/labels. Cumplir la regla `cpp_apps.md` y `apps_tbd.md`.
## Contexto
Toda la libreria de visualizacion ya existe en `cpp/functions/viz/` tras 0049bj. Esta app es el consumer final que orquesta:
- `graph_load_from_operations` para cargar datos.
- `types_registry` (modulo local) para mergear `types.yaml` opcional con tipos descubiertos.
- `graph_force_layout_gpu` (o CPU) corriendo segun toggle.
- `graph_renderer` + `graph_viewport` + `graph_labels` para visualizacion.
- `fn_ui::*` (toolbar, modal, select, etc.) para chrome.
## Arquitectura
```
projects/osint_graph/apps/graph_explorer/
├── .git/ # sub-repo dataforge/graph_explorer (creado en 0049a)
├── app.md # NEW
├── CMakeLists.txt # NEW
├── main.cpp # NEW
├── data.{h,cpp} # NEW: dispatcher GraphLoadFn
├── views.{h,cpp} # NEW: Toolbar, Legend, Inspector, Stats
├── types_registry.{h,cpp} # NEW: lee types.yaml y mergea
└── examples/
└── types.yaml # NEW: ejemplo OSINT con Person/Email/Domain/...
```
### `app.md` frontmatter
```yaml
---
name: graph_explorer
lang: cpp
domain: viz
description: "Visor de grafos GPU-accelerated agnostico del backend. Lee operations.db de cualquier app del registry y permite explorar entidades/relaciones con shapes/iconos/layouts/filtros."
tags: [imgui, graph, osint, visualization, gpu]
uses_functions:
- graph_renderer_cpp_viz
- graph_force_layout_cpp_viz
- graph_force_layout_gpu_cpp_viz
- graph_layouts_cpp_viz
- graph_viewport_cpp_viz
- graph_labels_cpp_viz
- graph_icons_cpp_viz
- graph_sources_cpp_viz
- toolbar_cpp_core
- modal_dialog_cpp_core
- select_cpp_core
- text_input_cpp_core
- tree_view_cpp_core
- page_header_cpp_core
- fullscreen_window_cpp_core
uses_types: []
framework: "imgui"
entry_point: "main.cpp"
dir_path: "projects/osint_graph/apps/graph_explorer"
repo_url: "https://gitea-.../dataforge/graph_explorer"
---
```
### CLI
```bash
graph_explorer [--input operations <path>] [--types <yaml>] [--layout force|grid|...]
graph_explorer apps/registry_dashboard/operations.db
graph_explorer --types projects/osint_graph/apps/graph_explorer/examples/types.yaml \
apps/element_agents/operations.db
```
### Layout de ventanas
```
┌──────────────────────────────────────────────────────────┐
│ MainMenuBar (View | Settings | About) │
├─[Toolbar: Open | Layout: [force▼] | Filters | Fit | …]──┤
├─Legend──┬───────────────────────────────────────┬─Inspector─┤
│ Type │ │ id: ... │
│ ☑ Person│ │ type: ... │
│ ☑ Email │ Viewport (FBO) │ metadata… │
│ ☐ Domain│ │ │
│ Rels: │ │ Neighbors:│
│ ☑ owns │ │ • node A │
│ ... │ │ • node B │
├─────────┴───────────────────────────────────────┴────────────┤
│ Stats: nodes=12345 edges=54321 fps=60 energy=0.04 sel=2 │
└──────────────────────────────────────────────────────────┘
```
## Tareas
### Fase 1 — Esqueleto
- [ ] **1.1** Crear `app.md` con frontmatter completo.
- [ ] **1.2** Crear `CMakeLists.txt` siguiendo `cpp_apps.md`. Listar todas las funciones del registry usadas explicitamente.
- [ ] **1.3** `main.cpp` minimo con `fn::run_app(cfg, render)` + parseo de `--input`/`--types`/`--layout`.
- [ ] **1.4** Registrar la app en `cpp/CMakeLists.txt` con el patron de `registry_dashboard`:
```cmake
set(_GE_DIR ${CMAKE_SOURCE_DIR}/../projects/osint_graph/apps/graph_explorer)
if(EXISTS ${_GE_DIR}/CMakeLists.txt)
add_subdirectory(${_GE_DIR} ${CMAKE_BINARY_DIR}/apps/graph_explorer)
endif()
```
### Fase 2 — `data.{h,cpp}` (dispatcher de sources)
- [ ] **2.1** Implementar dispatcher segun `--input`:
```cpp
bool load_graph(const InputArgs& args, GraphData* out, GraphLoadStats* stats) {
if (args.kind == INPUT_OPERATIONS) return graph_load_from_operations(args.uri, out, stats);
// futuro: json/jsonl/graphml
stats->errors++; return false;
}
```
- [ ] **2.2** Helper para reload (re-llamar la misma `GraphLoadFn` con la misma URI).
### Fase 3 — `types_registry.{h,cpp}`
- [ ] **3.1** Parser de `types.yaml`:
```yaml
entities:
- name: Person
color: "#5B8DEF"
shape: circle
icon: ti-user
- name: Email
color: "#58CA8C"
shape: square
icon: ti-mail
relations:
- name: owns
color: "#888888"
style: solid
```
- [ ] **3.2** Helper `apply_types_yaml(GraphData&, const ParsedTypes&)`: para cada `EntityType`/`RelationType` del grafo cuyo `name` matchee en el yaml, sobrescribir `color`/`shape`/`icon_id`/`style`. Tipos no encontrados se quedan con default (color por hash).
- [ ] **3.3** Iconos: mapear nombre `ti-user` → codepoint Tabler usando una tabla en el header `icons_tabler.h` (helpers ya existentes o anadir uno nuevo `tabler_codepoint_by_name(const char*)`).
- [ ] **3.4** Construccion del `IconAtlas`: collect codepoints distintos del yaml + fallback (icono "?" para no encontrados), llamar `graph_icons_build(...)`.
### Fase 4 — `views.{h,cpp}` (paneles)
- [ ] **4.1** **Toolbar**: `Open file…`, `Layout selector` (`select`), `Filters…` (modal con checkboxes por tipo), `Fit view`, `Save layout`, `Export PNG`.
- [ ] **4.2** **Legend**: lista de tipos con color swatch, icono y toggle de visibilidad. Toggle aplica a todos los nodos del tipo: `for each node where type_id == t: flags ^= NF_VISIBLE`. Igual para relaciones.
- [ ] **4.3** **Inspector**: cuando hay seleccion de un solo nodo, mostrar `id`, `type`, `metadata` (raw JSON formateado), lista de vecinos directos clickables (click cambia seleccion).
- [ ] **4.4** **Stats**: linea fija con counts + fps + energia.
- [ ] **4.5** Paneles toggleables via `AppConfig::panels`.
### Fase 5 — Persistencia
- [ ] **5.1** `graph_explorer.db` SQLite junto al exe con tabla `layouts(graph_hash TEXT, node_id TEXT, x REAL, y REAL, pinned INT, updated_at)`.
- [ ] **5.2** `graph_hash` = hash del path del input (operations.db) para diferenciar entre grafos.
- [ ] **5.3** `Save layout` boton en la toolbar: snapshot de posiciones + flags.
- [ ] **5.4** Al cargar un grafo conocido (mismo hash), pre-aplicar las posiciones guardadas.
- [ ] **5.5** Layout de paneles ImGui via `layout_storage` (ya existe como funcion publica desde 0042).
### Fase 6 — `types.yaml` ejemplo
- [ ] **6.1** Crear `examples/types.yaml` con ~10 tipos OSINT comunes (Person, Email, Domain, Phone, Org, IBAN, Account, Document, Address, Url) y 5 relaciones (owns, knows, located_in, transfers_to, member_of).
### Fase 7 — TBD trabajo
- [ ] **7.1** Trabajar en rama `quick/graph-explorer` o `issue/0049k-graph-explorer-app` segun preferencia.
- [ ] **7.2** Commits atomicos por panel/feature.
- [ ] **7.3** Merge `--no-ff` a master del sub-repo `dataforge/graph_explorer`.
- [ ] **7.4** Push del sub-repo + push de fn_registry con la nueva ubicacion + `app.md`.
### Fase 8 — Indexado + flag
- [ ] **8.1** `./fn index` desde la raiz.
- [ ] **8.2** Verificar:
```sql
SELECT id, name, project_id FROM apps WHERE id='graph_explorer_cpp_viz';
```
- [ ] **8.3** Activar feature flag en `dev/feature_flags.json`:
```json
"osint_graph_v1": { "enabled": true, "issue": "0049", ... }
```
- [ ] **8.4** Mover el issue principal 0049 + todos los sub-issues 0049ak a `dev/issues/completed/` y actualizar README.
### Fase 9 — Verificacion end-to-end
- [ ] **9.1** Abrir `apps/registry_dashboard/operations.db` y verificar que se ven entidades.
- [ ] **9.2** Abrir un dataset OSINT real (cuando exista) o un fixture en `~/vaults/osint_graph/raw/`.
- [ ] **9.3** Verificar todas las features: filtrado por tipo, drag, multi-select, lasso, layouts, labels, save/load layout.
## Criterio de done
- [ ] `graph_explorer apps/registry_dashboard/operations.db` arranca y muestra el grafo de entidades.
- [ ] Con `--types examples/types.yaml`, los tipos se ven con shapes/iconos/colores correctos.
- [ ] 50k nodos cargan y se navegan a 60fps con layout GPU.
- [ ] Layouts intercambiables en runtime via toolbar.
- [ ] Filtros, drag, multi-select, lasso, labels funcionando.
- [ ] Persistencia de layout entre sesiones.
- [ ] App registrada en registry.db con uses_functions correcto.
- [ ] Sub-repo `dataforge/graph_explorer` con master limpio y pushed.
- [ ] Feature flag `osint_graph_v1` = `true`.
- [ ] Issue 0049 + sub-issues movidos a completed.
## Riesgos
| Riesgo | Mitigacion |
|---|---|
| Parser YAML pesa: anadir libreria | Usar yaml-cpp si ya esta vendoreada; si no, parser minimal hand-rolled (10 tipos no necesita full YAML) |
| Mapeo `ti-user` → codepoint requiere tabla nueva | Generar a partir del `icons_tabler.h` existente con un script una sola vez |
| Inspector con metadata grande satura el panel | Truncar a ~512 chars + scroll del panel |
| Save layout en grafo grande es lento | UPSERT por nodo es O(N) con prepared statement; para 50k = ~100 ms aceptable en boton, no en frame |
| Operations.db de un app esta locked si la app corre | SQLite con `mode=ro` o `immutable=1` en el URI |