336051fef5
- 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>
232 lines
11 KiB
Markdown
232 lines
11 KiB
Markdown
# 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 0049b–j. 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 0049a–k 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 |
|